Testing @Postconstruct With Mockito

Mockito and Javax annotation @PostConstruct

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);
}
}

Mockito @Before method is called after @PostConstruct

Using @TestConfiguration rather than using the @MockBean might help to solve this problem.

@TestConfiguration
static class Configuration {
@Bean
public BeanToMock name() {
// return mock object
}
}

And annotating the test class with @ContextConfiguration:

@ContextConfiguration(classes = TestClassName.Configuration.class)


Related Topics



Leave a reply



Submit