using @InjectMocks outside @Before

ulquiorra :

I'm creating the base for my unit testing project( Spring boot rest controller) and I'm having a problem passing @InjectMocks value because it's only evaluated in @Before and therefore a nullpointer is thrown when i try to access it outside

Some tips to get around the problem please?
Thank you very much

Ps : Any other advices on best practices or something i did wrong for unit testing regarding my current base class test will be appreciated as well

Class to test (rest controller)

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

Abstract base test

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

Test class

@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";
    }
amseager :

This works as intended. However, you can setup mocks manually and inject them inside ConnectionManagementControllerImplTest constructor (before calling setTestClass(...)):

public ConnectionManagementControllerImplTest() {
    super();

    connectionManagementBusinessServiceMocked = Mockito.mock(ConnectionManagementBusinessService.class);

    connectionManagementControllerMocked = new ConnectionManagementControllerImpl();
    connectionManagementControllerMocked.setConnectionManagementBusinessService(connectionManagementBusinessServiceMocked);

    setTestClass(ConnectionManagementControllerImpl.class, connectionManagementControllerMocked);
}

Do not forget to remove @Mock and @InjectMocks annotations. Btw you can even remove @RunWith(MockitoJUnitRunner.class) in that case.

UPDATE: Both the constructor of test class and "init" method annotated with @Before are executed for each test. The difference is that Mockito annotations are processed between constructor and @Before method invocations.

So you can slightly change your code in order to achieve a positive result:

  1. Create "init" method (annotated with @Before) inside ConnectionManagementControllerImplTest and move setTestClass() into it from the constructor (in that particular case you can also remove the whole constructor because it would contain only super() invocation).
  2. Add super.init() after setTestClass() line (otherwise "init" method in the parent class will be ignored by JUnit).
  3. (Optional) you could also remove @Before annotation from the "init" method in the parent class if your tests are written in the same manner.

The example of code refactored in that way:

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. I'd prefer the former approach, but if you don't want to have a setter for ConnectionManagementBusinessService, you can choose the latter. I've tested both of them and the result was the same.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=159427&siteId=1