我正在为我的单元测试项目(Spring Boot Rest Controller)创建基础,并且在传递@InjectMocks值时遇到问题,因为仅在@Before中对它进行了评估,因此当我尝试访问它时会引发nullpointer外面
请提供一些解决问题的技巧?
非常感谢
Ps:关于最佳实践的任何其他建议或我在当前基础类测试中对单元测试做错的事情也将受到赞赏
要测试的类(休息控制器)
@RestController
@RequestMapping("/management")
@Api(description = "Users count connections", produces = "application/json", tags = {"ConnectionManagement API"})
public class ConnectionManagementControllerImpl implements ConnectionManagementController {
@Autowired
private ConnectionManagementBusinessService connectionManagementBusinessService;
@Override
@PostMapping(value = "/countConnectionsByInterval" , consumes = MediaType.TEXT_PLAIN_VALUE , produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ApiOperation(value = "count all users connections by interval")
public ResponseEntity<List<ConnectionsCountDto>> countConnectionsByInterval(@RequestBody String format) {
List<ConnectionsCountDto> connectionManagement = connectionManagementBusinessService.countConnectionsByInterval(format);
return new ResponseEntity<List<ConnectionsCountDto>>(connectionManagement, HttpStatus.OK);
}
抽象基础测试
public abstract class AbstractBaseTest<C> {
public MockMvc mockMvc;
private Class<C> clazz;
private Object inject;
protected abstract String getURL();
protected final void setTestClass(final Class<C> classToSet, final Object injectToSet) {
clazz = Preconditions.checkNotNull(classToSet);
inject = Preconditions.checkNotNull(injectToSet);
}
@Before
public void init() throws Exception {
MockitoAnnotations.initMocks(clazz);
mockMvc = MockMvcBuilders.standaloneSetup(inject).build();
}
protected MockHttpServletResponse getResponse(MediaType produces) throws Exception {
MockHttpServletResponse response = mockMvc.perform(
get(getURL()).
accept(produces)).
andReturn().
getResponse();
return response;
}
protected MockHttpServletResponse postResponse(String content , MediaType consumes , MediaType produces) throws Exception {
MockHttpServletResponse response = mockMvc.perform(
post(getURL()).
content(content).
contentType(consumes).
accept(produces)).
andReturn().
getResponse();
return response;
}
}
测试类
@RunWith(MockitoJUnitRunner.class)
public class ConnectionManagementControllerImplTest extends AbstractBaseTest<ConnectionManagementControllerImpl>{
@Mock
private ConnectionManagementBusinessService connectionManagementBusinessServiceMocked;
@InjectMocks
private ConnectionManagementControllerImpl connectionManagementControllerMocked;
public ConnectionManagementControllerImplTest() {
super();
setTestClass(ConnectionManagementControllerImpl.class , connectionManagementControllerMocked); // null pointer there
}
@Test
public void countConnectionsByInterval() throws Exception {
// given
given(connectionManagementBusinessServiceMocked.countConnectionsByInterval(Mockito.anyString()))
.willReturn(new ArrayList<ConnectionsCountDto>());
// when
MockHttpServletResponse response = postResponse("day" , MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON_UTF8);
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
}
@Override
protected String getURL() {
return "/management/countConnectionsByInterval";
}
答案 0 :(得分:1)
这按预期工作。但是,您可以手动设置模拟并将其注入ConnectionManagementControllerImplTest
构造函数中(在调用setTestClass(...)
之前):
public ConnectionManagementControllerImplTest() {
super();
connectionManagementBusinessServiceMocked = Mockito.mock(ConnectionManagementBusinessService.class);
connectionManagementControllerMocked = new ConnectionManagementControllerImpl();
connectionManagementControllerMocked.setConnectionManagementBusinessService(connectionManagementBusinessServiceMocked);
setTestClass(ConnectionManagementControllerImpl.class, connectionManagementControllerMocked);
}
不要忘记删除@Mock
和@InjectMocks
注释。在这种情况下,您甚至可以删除@RunWith(MockitoJUnitRunner.class)
。
更新:对于每个测试,都将同时执行测试类的构造函数和带有@Before
注释的“ init”方法。区别在于Mockito批注在构造函数和@Before
方法调用之间进行处理。
因此,您可以稍微更改代码以取得积极的结果:
@Before
内创建“ init”方法(用ConnectionManagementControllerImplTest
注释),并将setTestClass()
从构造函数移入其中(在这种情况下,您也可以删除整个构造函数,因为这样会仅包含super()
调用)。super.init()
行之后添加setTestClass()
(否则,JUnit将忽略父类中的“ init”方法)。@Before
注释。以这种方式重构的代码示例:
public abstract class AbstractBaseTest<C> {
public MockMvc mockMvc;
private Class<C> clazz;
private Object inject;
protected abstract String getURL();
protected final void setTestClass(final Class<C> classToSet, final Object injectToSet) {
clazz = Preconditions.checkNotNull(classToSet);
inject = Preconditions.checkNotNull(injectToSet);
}
@Before //this annotation can be removed
public void init() throws Exception {
MockitoAnnotations.initMocks(clazz); //this line also can be removed because MockitoJUnitRunner does it for you
mockMvc = MockMvcBuilders.standaloneSetup(inject).build();
}
protected MockHttpServletResponse getResponse(MediaType produces) throws Exception {
MockHttpServletResponse response = mockMvc.perform(
get(getURL()).
accept(produces)).
andReturn().
getResponse();
return response;
}
protected MockHttpServletResponse postResponse(String content , MediaType consumes , MediaType produces) throws Exception {
MockHttpServletResponse response = mockMvc.perform(
post(getURL()).
content(content).
contentType(consumes).
accept(produces)).
andReturn().
getResponse();
return response;
}
}
@RunWith(MockitoJUnitRunner.class)
public class ConnectionManagementControllerImplTest extends AbstractBaseTest<ConnectionManagementControllerImpl> {
@Mock
private ConnectionManagementBusinessService connectionManagementBusinessServiceMocked;
@InjectMocks
private ConnectionManagementControllerImpl connectionManagementControllerMocked;
//constructor can be removed
public ConnectionManagementControllerImplTest() {
super();
}
@Before
public void init() throws Exception {
setTestClass(ConnectionManagementControllerImpl.class, connectionManagementControllerMocked);
super.init();
}
@Test
public void countConnectionsByInterval() throws Exception {
// given
given(connectionManagementBusinessServiceMocked.countConnectionsByInterval(Mockito.anyString()))
.willReturn(new ArrayList<ConnectionsCountDto>());
// when
MockHttpServletResponse response = postResponse("day", MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON_UTF8);
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
}
@Override
protected String getURL() {
return "/management/countConnectionsByInterval";
}
}
P.S。我希望使用前一种方法,但是如果您不想为ConnectionManagementBusinessService
使用设置器,则可以选择后者。我已经测试了它们两个,结果是一样的。