В чем разница между Mockito Matchers isA, any, eq и same?

Я запутался, в чем разница между ними, и какой выбрать в каком случае. Некоторые различия могут быть очевидны, например any и eq, но я включаю их все, чтобы быть уверенным.

Меня интересуют их различия, потому что я столкнулся с этой проблемой: у меня есть этот метод POST в классе контроллера.

public Response doSomething(@ResponseBody Request request) {
    return someService.doSomething(request);
}

И хотел бы выполнить модульный тест на этом контроллере. У меня есть две версии. Первый самый простой, вот такой

@Test
public void testDoSomething() {
    //initialize ObjectMapper mapper
    //initialize Request req and Response res
    
    when(someServiceMock.doSomething(req)).thenReturn(res);

    Response actualRes = someController.doSomething(req);
    assertThat(actualRes, is(res));
}

Но я хотел использовать подход MockMvc, как этот

@Test
public void testDoSomething() {
    //initialize ObjectMapper mapper
    //initialize Request req and Response res
    
    when(someServiceMock.doSomething(any(Request.class))).thenReturn(res);

    mockMvc.perform(post("/do/something")
            .contentType(MediaType.APPLICATION_JSON)
            .content(mapper.writeValueAsString(req))
    )
            .andExpect(status().isOk())
            .andExpect(jsonPath("$message", is("done")));
}

Оба работают хорошо. Но я хотел, чтобы мой someServiceMock.doSomething() в подходе MockMvc получал req или, по крайней мере, объект с теми же значениями переменных, что и req (а не просто любой класс Request), и возвращал res, как и первый. Я знаю, что невозможно использовать подход MockMvc (или нет?), потому что объект, переданный в фактическом вызове, всегда отличается от объекта, переданного в макете. Могу ли я в любом случае достичь этого? Или вообще есть смысл это делать? Или я должен быть удовлетворен использованием any(Request.class)? Я пробовал eq, same, но все они терпят неудачу.


person Silly Sally    schedule 17.06.2015    source источник


Ответы (2)


  • any() проверяет совершенно ничего. Начиная с Mockito 2.0, any(T.class) разделяет семантику isA и означает любой T или, собственно, любой экземпляр типа T.

    Это изменение по сравнению с Mockito 1.x, где any(T.class) абсолютно ничего не проверял, но сохранял приведение до Java 8: объект любого вида, не необходимое данного класса. Аргумент класса предоставляется только для того, чтобы избежать приведения типов.

  • isA(T.class) проверяет, что аргумент instanceof T не равен нулю.

  • same(obj) проверяет, что аргумент относится к тому же экземпляру, что и obj, так что arg == obj истинно.

  • eq(obj) проверяет, что аргумент равен obj в соответствии со своим методом equals. Это также поведение, если вы передаете реальные значения без использования сопоставителей.

    Обратите внимание, что если equals не переопределено, вы увидите реализацию Object.equals по умолчанию, которая будет вести себя так же, как same(obj).

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

  • Для Mockito 1.x используйте argThat с пользовательским Hamcrest Matcher<T>, который выбирает именно те объекты, которые вам нужны.
  • Для Mockito 2.0 и более поздних версий используйте Matchers.argThat с пользовательским org.mockito.ArgumentMatcher<T> или MockitoHamcrest.argThat с пользовательским Hamcrest Matcher<T>.

Вы также можете использовать refEq, который использует отражение для подтверждения равенства объектов; У Hamcrest есть аналогичная реализация с SamePropertyValuesAs для общедоступного компонента. -свойства стиля. Обратите внимание, что на GitHub в выпуске №1800 предлагается отказаться от поддержки и удалить refEq, и, как и в этом выпуске вы можете предпочесть eq, чтобы дать вашим классам лучшую инкапсуляцию, а не их чувство равенства.

person Jeff Bowman    schedule 17.06.2015
comment
Это практический ответ: не только дифференциация 4 сопоставителей аргументов Mockito, но и намек на риски с помощью equals и настройка с помощью argThat ???? - person hc_dev; 30.01.2021
comment
Пожалуйста, добавьте refEq() Этот сопоставитель можно использовать, когда функция equals() не реализована для сравниваемых объектов. Matcher использует API отражения Java для сравнения полей желаемого и фактического объекта. - person Ivan; 19.05.2021
comment
@Ivan Хотя этого не было в исходном вопросе, я добавил упоминание refEq, включая предложение объявить его устаревшим и удалить из Mockito. - person Jeff Bowman; 19.05.2021

Если ваш Request.class реализует equals, вы можете использовать eq():

Bar bar = getBar();
when(fooService.fooFxn(eq(bar)).then...

Приведенный выше когда активируется

fooService.fooFxn(otherBar);

if

otherBar.equals(bar);

В качестве альтернативы, если вы хотите, чтобы макет работал для какого-либо другого подмножества ввода (например, для всех баров с Bar.getBarLength()>10), вы можете создать Matcher. Я не слишком часто вижу этот шаблон, поэтому обычно я создаю Matcher как частный класс:

private static class BarMatcher extends BaseMatcher<Bar>{
...//constructors, descriptions, etc.
  public boolean matches(Object otherBar){
     //Checks, casts, etc.
     return otherBar.getBarLength()>10;
  }
}

Затем вы должны использовать этот сопоставитель следующим образом:

when(fooService.fooFxn(argThat(new BarMatcher())).then...

Надеюсь, это поможет!

person Alex Pruss    schedule 17.06.2015