Mockito 和 Javax 注解 @PostConstruct
Mockito and Javax annotation @PostConstruct
我正在使用 JUnit Jupiter 为 class 编写一个 Java 单元测试,它有一个带有 javax.annotation.PostConstruct
注释的方法。
我想测试 class 中的一个方法,但我遇到了一个问题,即 @PostConstruct
方法总是在对象创建后被调用(不出所料)。我创建的用于 @PostConstruct
方法的 Mockito 模拟对象没有提供我需要的结果。
创建对象时调用的方法如下:
@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...
}
}
我的 JUnit 测试 class 包含以下内容:
@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()
}
}
当我 运行 单元测试时,我可以看到模拟已通过我的 IDE 实例化。但是,当调用 findByName("FOO")
方法时,它始终 return 是一个 NULL 对象,即使它应该 return 是 MyObject
.
的正确实例
因此,单元测试因 NullPointerException 而失败,因为 myObj
为空。
我在这里做的有没有明显不正确的地方>?
在此先感谢您的帮助。
我稍微修改了您的测试,通过 Mockito
创建了 MyService
并使 myMethod
return 成为 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);
}
}
当我 运行 这个时,我得到:
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)
...
很明显,我们对 findByName
的嘲讽没有奏效。
那是因为 myRepository
在我们的测试中只是一个局部变量——没有什么可以将它注入 MyService
。
解决这个问题的一种方法是不使用 spring 测试基础设施,只使用普通的 Mockito。我们使用标准的@Mock/@InjectMocks 注释来创建我们的模拟和我们正在测试的对象。这里的缺点是我们必须记得自己调用postInit
:
@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);
}
}
要使用 Spring 调用 postInit,您可以这样做:(我不确定是否有更惯用的方法来创建模拟 MyRepository 实例)
@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);
}
}
避免使用 @SpringBootTest
的一种方法是在我们的测试配置中创建 MyService
实例(我也将其转换为构造函数注入):
@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);
}
}
我正在使用 JUnit Jupiter 为 class 编写一个 Java 单元测试,它有一个带有 javax.annotation.PostConstruct
注释的方法。
我想测试 class 中的一个方法,但我遇到了一个问题,即 @PostConstruct
方法总是在对象创建后被调用(不出所料)。我创建的用于 @PostConstruct
方法的 Mockito 模拟对象没有提供我需要的结果。
创建对象时调用的方法如下:
@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...
}
}
我的 JUnit 测试 class 包含以下内容:
@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()
}
}
当我 运行 单元测试时,我可以看到模拟已通过我的 IDE 实例化。但是,当调用 findByName("FOO")
方法时,它始终 return 是一个 NULL 对象,即使它应该 return 是 MyObject
.
因此,单元测试因 NullPointerException 而失败,因为 myObj
为空。
我在这里做的有没有明显不正确的地方>?
在此先感谢您的帮助。
我稍微修改了您的测试,通过 Mockito
创建了 MyService
并使 myMethod
return 成为 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);
}
}
当我 运行 这个时,我得到:
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)
...
很明显,我们对 findByName
的嘲讽没有奏效。
那是因为 myRepository
在我们的测试中只是一个局部变量——没有什么可以将它注入 MyService
。
解决这个问题的一种方法是不使用 spring 测试基础设施,只使用普通的 Mockito。我们使用标准的@Mock/@InjectMocks 注释来创建我们的模拟和我们正在测试的对象。这里的缺点是我们必须记得自己调用postInit
:
@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);
}
}
要使用 Spring 调用 postInit,您可以这样做:(我不确定是否有更惯用的方法来创建模拟 MyRepository 实例)
@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);
}
}
避免使用 @SpringBootTest
的一种方法是在我们的测试配置中创建 MyService
实例(我也将其转换为构造函数注入):
@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);
}
}