I'm writing a Java unit test using JUnit Jupiter for a class which has a method with a javax.annotation.PostConstruct
annotation.
I want to test a method in the class but am hitting an issue where the @PostConstruct
method is always called once the object is created (unsurprisingly). The Mockito mock object I've created to be used in the @PostConstruct
method isn't providing the results I require.
Here's the method that is being called when the object is created:
@Service
@Slf4j
public class MyService {
@Inject
private MyRepository myRepository;
private Integer myId;
@PostConstruct
public void postInit() {
MyObject myObj = myRepository.findByName("FOO");
myId = myObj.getId();
}
public void myMethod() {
... code to be tested here...
}
}
My JUnit test class contains the following:
@ExtendWith(SpringExtension.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class MyServiceTest {
private static final Integer ID = 0;
@Test
public void myTest() {
// Given.
MyObject myObj = new MyObject();
myObj.setId(ID);
MyRepository myRepository = Mockito.mock(MyRepository.class);
Mockito.when(myRepository.findByName(anyString())).thenReturn(myObj);
// Test runs here.
myService.myMethod()
}
}
When I run the unit test, I can see that the mock has been instantiated via my IDE. However, it always returns a NULL object when the findByName("FOO")
method is called, even though it should return a correct instance of MyObject
.
As such, the unit test fails with a NullPointerException because myObj
is null.
Is there anything obviously incorrect that I'm doing here>?
Thanks in advance for any assistance.
I've modified your test slightly, creating MyService
via Mockito
and making myMethod
return the id:
@ExtendWith(SpringExtension.class)
class MyServiceTest {
private static final Integer ID = 42;
@InjectMocks
MyService myService;
@Test
public void myTest() {
// Given.
MyObject myObj = new MyObject();
myObj.setId(ID);
MyRepository myRepository = Mockito.mock(MyRepository.class);
Mockito.when(myRepository.findByName(anyString())).thenReturn(myObj);
// Test runs here.
assertThat(myService.myMethod()).isEqualTo(ID);
}
}
When I run this, I get:
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.myId" is null
at MyService.myMethod(MyService.java:24)
at MyServiceTest.myTest(MyServiceTest.java:28)
...
So clearly our mocking of findByName
hasn't worked.
That's because myRepository
is just a local variable in our test -- there's nothing to get it injected into MyService
.
One way to fix this is not to use spring test infrastructure, just plain Mockito. We use the standard @Mock/@InjectMocks annotations to create our mock and the object we are testing. The drawback here is that we must remember to call postInit
ourselves:
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
private static final Integer ID = 42;
@Mock
MyRepository myRepository;
@InjectMocks
MyService myService;
@Test
public void myTest() {
// Given.
MyObject myObj = new MyObject();
myObj.setId(ID);
Mockito.when(myRepository.findByName(anyString())).thenReturn(myObj);
myService.postInit();
// Test runs here.
assertThat(myService.myMethod()).isEqualTo(ID);
}
}
To use Spring to call postInit you can do this: (I'm not sure if there's a more idiomatic way to create the mock MyRepository instance)
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {MyService.class})
@ContextConfiguration(classes = {MyServiceTest.Configuration.class})
class MyServiceTest {
@TestConfiguration
static class Configuration {
@Bean
public MyRepository myRepository() {
MyRepository myRepository = mock(MyRepository.class);
MyObject myObj = new MyObject();
myObj.setId(ID);
Mockito.when(myRepository.findByName(anyString())).thenReturn(myObj);
return myRepository;
}
}
private static final Integer ID = 42;
@Autowired
MyService myService;
@Test
public void myTest() {
assertThat(myService.myMethod()).isEqualTo(ID);
}
}
One way to avoid using @SpringBootTest
is to create the MyService
instance in our test configuration (I've converted it to constructor injection too):
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MyServiceTest.Configuration.class})
class MyServiceTest {
@TestConfiguration
static class Configuration {
@Bean
public MyRepository myRepository() {
MyRepository myRepository = mock(MyRepository.class);
MyObject myObj = new MyObject();
myObj.setId(ID);
Mockito.when(myRepository.findByName(anyString())).thenReturn(myObj);
return myRepository;
}
@Bean
public MyService myService(MyRepository myRepository) {
return new MyService(myRepository);
}
}
private static final Integer ID = 42;
@Autowired
MyService myService;
@Test
public void myTest() {
assertThat(myService.myMethod()).isEqualTo(ID);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.