Reputation: 1667
I have a Spring boot service defined like this
@Service
public class MyService {
private String field1;
private String field2;
@Autowired
private AnotherService anotherService
@PostConstruct
public void init() {
anotherService.initField1(field1);
anotherService.initField2(field2);
}
public String foo() {
return field1 + field2;
}
}
How should I write a unit test for foo
. Well, it's more about how to deal with class fields and the PostConstruct
methods.
Thanks!!
EDIT:
Added AnotherService
as a field as well.
Upvotes: 1
Views: 939
Reputation: 24453
The following example shows a @Service
Bean that uses constructor injection to obtain a required AnotherService
bean:
@Service
public class MyService {
private String field1;
private String field2;
private final AnotherService anotherService;
public MyService(AnotherService anotherService) {
this.anotherService = anotherService;
this.anotherService.initField1(field1);
this.anotherService.initField2(field2);
}
public String foo() {
return field1 + field2;
}
}
Note you can omit the @Autowired
becuase MyService
has one constructor. See here for more info.
testing with Spring
Use the @RunWith(SpringRunner.class) and @SpringBootTest to inject MyService
and start using it:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest {
@Autowired
private MyService service;
@Test
public void testFoo() {
String expResult = "";
String result = service.foo();
assertEquals(expResult, result);
}
}
testing without Spring
public class MyServiceTest2 {
private MyService service;
@Before
public void setUp() {
service = new MyService(new AnotherService.Fake());
}
@Test
public void testFoo() {
String expResult = "";
String result = service.foo();
assertEquals(expResult, result);
}
}
Here Fake
is a fake implementation of the AnotherService
interface which allows you to have a pure unit test.
Upvotes: 3
Reputation: 1414
Writing good, testable code can be hard. There are some pitfalls waiting for everyone to fall into sooner or later.
As a rule of thumb, try to avoid field level injection, use constructor parameter injection instead:
@Service
public class MyService {
private AnotherService anotherService;
@Autowired
MyService (AnotherService anotherService) {
this.anotherService = anotherService;
}
}
This is the cleanest solution. You can call the constructor from your tests, spring will inject dependencies the same way at runtime. So there is no difference to deal with.
The same goes for any life cycle constructs like @PostConstruct
. If you can avoid them, do it. Let the constructor handle it. If you absolutely have to keep them around, well, the only logical solution is to manually call them from your test code.
Now, how to setup services that at runtime would be autowired by the container?
For unit testing, you basically have three options (in no particular order):
If the required service is rather simple and can easily be constructed, create and pass it as the framework would do.
If the service has a limited interface that does not change too often, create a fake service.
Use a mocking lib like mockito (spring-boot-test provides it by default).
Upvotes: 1