используя @InjectMocks за пределами @Before - PullRequest
2 голосов
/ 05 марта 2019

Я создаю базу для моего проекта модульного тестирования (контроллер начальной загрузки Spring), и у меня возникла проблема с передачей значения @InjectMocks, поскольку оно оценивается только в @Before и, следовательно, при попытке получить к нему нулевой указатель вне

Несколько советов, как обойти проблему, пожалуйста?
Большое спасибо

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 Ответ

1 голос
/ 05 марта 2019

Это работает как задумано.Однако вы можете настроить макеты вручную и вставить их в конструктор 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) в этом случае.

ОБНОВЛЕНИЕ: И конструктор класса теста, и метод "init", помеченные @Before, выполняются для каждого теста.Разница в том, что аннотации Mockito обрабатываются между вызовами методов конструктора и @Before.

Таким образом, вы можете немного изменить свой код для достижения положительного результата:

  1. Создайте метод "init" (помеченный @Before) внутри ConnectionManagementControllerImplTest и переместите setTestClass() в него из конструктора (в этом конкретном случае вы также можете удалить весь конструктор, потому что он будет содержать только super() вызов).
  2. Добавить super.init() после setTestClass() строки (в противном случае метод "init"в родительском классе будет игнорироваться JUnit).
  3. (Необязательно) вы также можете удалить аннотацию @Before из метода "init" в родительском классе, если ваши тесты написаны таким же образом.

Пример кода, реорганизованного таким образом:

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

PS Я бы предпочел первый подход, но если вы не хотите иметь установщик для ConnectionManagementBusinessService, вы можете выбрать последнее.Я проверил их обоих, и результат был одинаковым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...