Как проверить if-оператор в методе с помощью Mockito и JUnit?

#java #unit-testing #junit #mockito

#java #модульное тестирование #junit #mockito

Вопрос:

У меня есть метод, который я должен протестировать. Код (конечно, некоторые части были вырезаны):

 public class FilterDataController {

    public static final String DATE_FORMAT = "yyyy-MM-dd";

    @Autowired
    private FilterDataProvider filterDataProvider;

    @ApiOperation(value = "Get possible filter data",response = ResponseEntity.class)
    @ApiResponses(value = {
            @ApiResponse(...),
            @ApiResponse(...)})
    @RequestMapping(path = "...", method = RequestMethod.GET)
    public ResponseEntity<Object> getPossibleFilterData(
            @RequestParam(value = "startDate") @DateTimeFormat(pattern=DATE_FORMAT) final Date startDate,
            @RequestParam(value = "endDate") @DateTimeFormat(pattern=DATE_FORMAT) final Date endDate) {
        if (endDate.compareTo(startDate) == -1){
            throw new ValueNotAllowedException("End date should be after or equal start date");
        }
        else {
            Date newEndDate = endDate;
            if (startDate.equals(endDate)){
                newEndDate = new Date(endDate.getTime()   TimeUnit.DAYS.toMillis(1) - 1);
            }

            List<String> possibleCountries = Lists.newArrayList(filterDataProvider.getPossibleCountries(startDate, newEndDate));

            return new ResponseEntity<>(new FilterResponse(possibleCountries),HttpStatus.OK);
        }
    }   
}
  

Вопрос: как проверить if-оператор в методе getPossibleFilterData с помощью Mockito и JUnit? Я хочу передать методу равные даты, а затем проверить, правильно ли работает мой if-оператор.

Комментарии:

1. Как упоминалось во многих ответах ниже, ваши тесты не должны предполагать вашу реализацию: что, если вы реорганизуете метод, чтобы он возвращал правильные значения, но вообще не использовал if оператор? Вместо того, чтобы предполагать это, передайте ошибочные значения и пройдите тест только тогда, когда поймаете правильное исключение .

2. @JeffBowman спасибо за совет.

Ответ №1:

Если вы действительно хотите чистый модульный тест, а не интеграционный тест, вы можете полагаться на аннотацию @Mock , чтобы смоделировать свой сервис FilterDataProvider и @InjectMocks внедрить свой макет в свой экземпляр FilterDataController .

Тогда вы могли бы предложить 3 теста:

  1. Один тест, в котором даты указаны правильно, но разные,
  2. Еще один, где даты исправлены, но равны
  3. И последний, где даты неверны, который будет выброшен ValueNotAllowedException , который можно было бы протестировать из коробки, используя @Test(expected = ValueNotAllowedException.class) .

Если вам нужно убедиться, что filterDataProvider.getPossibleCountries(startDate, newEndDate) он был вызван с ожидаемыми аргументами, которые вам нужно использовать verify .

Тогда код будет примерно таким:

 @RunWith(MockitoJUnitRunner.class)
public class FilterDataControllerTest {
    @Mock
    FilterDataProvider filterDataProvider;
    @InjectMocks
    FilterDataController controller;

    @Test(expected = ValueNotAllowedException.class)
    public void testGetPossibleFilterDataIncorrectDates() {
        controller.getPossibleFilterData(new Date(1L), new Date(0L));
    }

    @Test
    public void testGetPossibleFilterDataCorrectDates() {
        // Make the mock returns a list of fake possibilities
        Mockito.when(
            filterDataProvider.getPossibleCountries(
                Mockito.anyObject(), Mockito.anyObject()
            )
        ).thenReturn(Arrays.asList("foo", "bar"));
        ResponseEntity<Object> response = controller.getPossibleFilterData(
            new Date(0L), new Date(1L)
        );
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
        // Make sure that 
        // filterDataProvider.getPossibleCountries(new Date(0L), new Date(1L))
        // has been called as expected
        Mockito.verify(filterDataProvider).getPossibleCountries(
            new Date(0L), new Date(1L)
        );
        // Test response.getBody() here
    }

    @Test
    public void testGetPossibleFilterDataEqualDates() {
        // Make the mock returns a list of fake possibilities
        Mockito.when(
            filterDataProvider.getPossibleCountries(
                Mockito.anyObject(), Mockito.anyObject()
            )
        ).thenReturn(Arrays.asList("foo", "bar"));
        // Call the controller with the same dates
        ResponseEntity<Object> response = controller.getPossibleFilterData(
            new Date(1L), new Date(1L)
        );
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
        Mockito.verify(filterDataProvider).getPossibleCountries(
            new Date(1L), new Date(TimeUnit.DAYS.toMillis(1))
        );
        // Test response.getBody() here
    }
}
  

Комментарии:

1. спасибо за код. Но как проверить, что filterDataProvider это было вызвано с правильными датами? Я хочу отправить на getPossibleFilterData равные даты, а затем проверить, что filterDataProvider.getPossibleCountries(startDate, newEndDate) вызывается с исправленными датами.

Ответ №2:

Вам придется издеваться FilterDataProvider , а затем вводить это в свой тестовый класс с помощью InjectMocks.

getPossibleFilterData это будет тестируемый метод, поэтому выберите любую конкретную дату (используйте Calendar.set(...) , затем Calendar.getTime() ) и отправьте эту же дату в качестве начальной и конечной даты.

Теперь после getPossibleFilterData завершения вы можете проверить, был ли filterDataProvider.getPossibleCountries вызван с конечной датой, которая на одну миллисекунду больше даты начала. Это можно сделать Calendar.getTimeInMillis() внутри метода mocked класса или путем проверки с помощью Mockito с датой, которая на одну миллисекунду больше, чем дата, которая была указана изначально.

Редактировать: приведен пример кода:

 public class FilterDataControllerTest {
    @Test
    public void testSameDate() {
        FilterDataProvider provider = Mockito.mock(FilterDataProvider.class);
        FilterDataController controller = new FilterDataController(provider);

        Date startDate = new GregorianCalendar(2016, Calendar.JANUARY, 11).getTime();
        Date endDate = new GregorianCalendar(2016, Calendar.JANUARY, 11).getTime();
        Date expectedEndDate = new Date(endDate.getTime()   TimeUnit.DAYS.toMillis(1) - 1);

        controller.getPossibleFilterData(startDate, endDate);

        Mockito.verify(provider).getPossibleCountries(Mockito.eq(startDate), Mockito.eq(expectedEndDate));
    }
}
  

Комментарии:

1. «или путем проверки с помощью Mockito» — можете ли вы объяснить, как это сделать? Мой первоначальный вопрос был об этом (как проверить, что EndDate был изменен в случае EndDate==StartDate . Такой случай соответствует внутреннему if-оператору).

2. Для вашего кода требуется написать конструктор FilterDataController , но в моей ситуации это неуместно. Но спасибо за такой подход. Я запомню это. 1

Ответ №3:

Я вижу два основных подхода.

  1. Использование функциональности Mockito: если вы вводите в свой контроллер макет FilterDataProvider (что является стандартным подходом, использующим, например, MockitoJUnitRunner и @InjectMocks), вы можете использовать опцию Mockito «verity», чтобы убедиться, что он получил правильную конечную дату. Смотрите обсуждение: http://www.vogella.com/tutorials/Mockito/article.html#mockito_verify
  2. Очевидно, что существуют другие подходы, которые основаны на логике, а не на технических деталях. Например: рефакторинг части «if» в отдельный метод «correctEndDate» или заполнение ваших данных так, чтобы на основе конечной даты возвращался другой список стран.

Комментарии:

1. спасибо за verify . Есть пример verify(test).testing(Matchers.eq(12)); . Но в моем случае я хочу убедиться, что метод в моем методе был вызван с исправленными датами. Может быть, что-то вроде этого newEndDate = ...; verify(FilterDataController).getPossibleFilterData().getPossibleCountries(Matchers.eq(startDate, newEndDate));