在@Before外使用@InjectMocks

时间:2019-03-05 16:14:34

标签: java unit-testing junit

我正在为我的单元测试项目(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";
    }

1 个答案:

答案 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方法调用之间进行处理。

因此,您可以稍微更改代码以取得积极的结果:

  1. @Before内创建“ init”方法(用ConnectionManagementControllerImplTest注释),并将setTestClass()从构造函数移入其中(在这种情况下,您也可以删除整个构造函数,因为这样会仅包含super()调用)。
  2. super.init()行之后添加setTestClass()(否则,JUnit将忽略父类中的“ init”方法)。
  3. (可选)如果您以相同的方式编写测试,也可以从父类的“ 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使用设置器,则可以选择后者。我已经测试了它们两个,结果是一样的。