Может ли Mockito проверить, что аргумент имеет определенные свойства/поля?

Скажем, я издеваюсь над этим классом Foo

class Foo {
  public void doThing(Bar bar) {
    // ...
  }
}

и это Bar

class Bar {
  private int i;
  public int getI() { return i; }
  public void setI(int i) { this.i = i; }
}

Я знаю, что могу использовать функцию проверки Mockito, чтобы узнать, был ли Foo#doThing(Bar) на макет с конкретным экземпляром Bar или любой Bar с Mockito.any(Bar.class), но есть ли способ убедиться, что это было вызываемый любой Bar но с определенным значением для i или Bar#getI()?

То, что я знаю, возможно:

Foo mockedFoo = mock(Foo.class);
Bar someBar = mock(Bar.class);
...
verify(mockedFoo).doThing(someBar);
verify(mockedFoo).doThing(any(Bar.class);

Я хочу знать, есть ли способ проверить, что Bar с определенными вещами, истинными в этом отношении, был передан в качестве аргумента.

Ответ 1

В Mockito 2.1.0 и более поздних версиях Java 8 вы можете передавать лямбду-аргумент argThat из коробки, чтобы не требовалось сопоставления пользовательских аргументов. Для примера в ОП будет:

verify(mockedFoo).doThing(argThat((Bar aBar) -> aBar.getI() == 5));

Это связано с тем, что в Mockito 2.1.0 ArgumentMatcher является функциональным интерфейсом.

Ответ 2

Если вы используете Mockito 2.1.0 или выше и Java 8 или выше, см. Этот ответ, вместо этого он намного проще.


Я нашел ответ, когда писал вопрос.

Да, ты можешь. Вместо использования any(Bar.class) вам нужно реализовать собственный экземпляр ArgumentMatcher<T> и использовать Mockito#argThat(Matcher), например, мы хотим проверить, что i 5...

// in the test (could also be outside)

private static final class BarIs5 extends ArgumentMatcher<Bar> {

  @Override
  public boolean matches(Object argument) {
    return ((Bar) argument).getI() == 5;
  }
}

Затем verify(mockedFoo).doThing(argThat(new BarIs5())); следующие действия: verify(mockedFoo).doThing(argThat(new BarIs5()));


Ударьте его с надстройкой, добавив параметры конструктора!

private static final class BarIsWhat extends ArgumentMatcher<Bar> {

  private final int i;

  public BarIsWhat(int i) {
    this.i = i
  }

  @Override
  public boolean matches(Object argument) {
    return ((Bar) argument).getI() == i;
  }
}

Затем verify(mockedFoo).doThing(argThat(new BarIsWhat(5))); следующие действия: verify(mockedFoo).doThing(argThat(new BarIsWhat(5)));


Обновление: это появилось в моей очереди из-за значка и увидело некоторое место для улучшения.

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

Единственная проблема в том, что argThat принимает Hamcrest Matcher, который не является @FunctionalInterface. К счастью, Mockito ArgumentMatcher является абстрактным классом, который расширяет его и имеет только один абстрактный метод.

В вашем тесте (или в каком-то общем месте) сделайте такой метод, как ниже

private static <T> ArgumentMatcher<T> matches(Predicate<T> predicate) {
  return new ArgumentMatcher<T>() {

    @SuppressWarnings("unchecked")
    @Override
    public boolean matches(Object argument) {
      return predicate.test((T) argument);
    }
  };
}

Теперь в вашем тесте вы можете сделать это, чтобы использовать выражение лямбда:

verify(mockedFoo).doThing(argThat(matches( (Bar arg) -> arg.getI() == 5 )));

Ответ 3

Если вы не можете использовать Mockito 2+, вы также можете использовать старый добрый ArgumentCaptor. Это будет немного более многословно, хотя:

ArgumentCaptor<Long> siteIdCaptor = ArgumentCaptor.forClass(Long.class);
verify(repository).findBySiteId(siteIdCaptor.capture());
assertEquals(15, siteIdCaptor.getValue().longValue());