Сопоставители Mockito - это статические методы и вызовы этих методов, которые заменяют аргументы во время вызовов when
и verify
.
Сопоставители Hamcrest (заархивированная версия) (или сопоставители в стиле Hamcrest) представляют собой экземпляры объектов общего назначения без сохранения состояния, которые реализуют Matcher<T>
и предоставляют метод, matches(T)
который возвращает true, если объект соответствует критериям сопоставления. Предполагается, что они не имеют побочных эффектов и обычно используются в утверждениях, подобных приведенному ниже.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Сопоставители Mockito существуют отдельно от сопоставителей в стиле Hamcrest, поэтому описания сопоставлений выражений вписываются непосредственно в вызовы методов : сопоставители Mockito возвращают, T
где методы сопоставления Hamcrest возвращают объекты сопоставления (типа Matcher<T>
).
Mockito matchers вызываются через статические методы , такие как eq
, any
, gt
, и startsWith
на org.mockito.Matchers
и org.mockito.AdditionalMatchers
. Также существуют адаптеры, которые изменились в версиях Mockito:
- Для Mockito 1.x
Matchers
некоторые вызовы (например, intThat
или argThat
) представляют собой сопоставители Mockito, которые напрямую принимают сопоставители Hamcrest в качестве параметров. ArgumentMatcher<T>
расширенный org.hamcrest.Matcher<T>
, который использовался во внутреннем представлении Hamcrest и был базовым классом сопоставления Hamcrest вместо какого-либо сопоставления Mockito.
- Для Mockito 2.0+ Mockito больше не имеет прямой зависимости от Hamcrest.
Matchers
вызовы в любой формулировке intThat
или argThat
обертка ArgumentMatcher<T>
не возражает , что больше не реализовать , org.hamcrest.Matcher<T>
но используются аналогичным образом. Адаптеры Hamcrest, такие как argThat
и intThat
, все еще доступны, но были заменены на них MockitoHamcrest
.
Независимо от того, являются ли совпадения Hamcrest или просто Hamcrest-style, они могут быть адаптированы следующим образом:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
В приведенном выше заявлении: foo.setPowerLevel
- это метод, который принимает расширение int
. is(greaterThan(9000))
возвращает a Matcher<Integer>
, который не работает в качестве setPowerLevel
аргумента. Сопоставитель Mockito intThat
обертывает этот сопоставитель в стиле Hamcrest и возвращает, int
чтобы он мог выступать в качестве аргумента; Сопоставители Mockito как gt(9000)
бы оборачивают все это выражение в один вызов, как в первой строке примера кода.
Что делают / возвращают сопоставители
when(foo.quux(3, 5)).thenReturn(true);
Когда не используются сопоставители аргументов, Mockito записывает значения ваших аргументов и сравнивает их со своими equals
методами.
when(foo.quux(eq(3), eq(5))).thenReturn(true); // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different
Когда вы вызываете сопоставление типа any
или gt
(больше), Mockito сохраняет объект сопоставления, который заставляет Mockito пропустить эту проверку равенства и применить выбранное сопоставление. В случае, если argumentCaptor.capture()
он хранит сопоставление, которое вместо этого сохраняет свой аргумент для последующей проверки.
Сопоставители возвращают фиктивные значения, такие как ноль, пустые коллекции или null
. Mockito пытается вернуть безопасное и подходящее фиктивное значение, например 0 для anyInt()
или any(Integer.class)
или пустое значение List<String>
для anyListOf(String.class)
. Из - за типа стирания, хотя, Mockito не хватает информации о типе , чтобы возвратить любое значение , но null
для any()
или argThat(...)
, что может привести к NullPointerException , если пытаться «авто-Unbox» а null
примитивное значение.
Матчеры любят eq
и gt
принимают значения параметров; в идеале эти значения должны быть вычислены до начала заглушки / проверки. Вызов имитации в процессе имитации другого звонка может помешать заглушке.
Методы сопоставления не могут использоваться как возвращаемые значения; нет возможности сформулировать thenReturn(anyInt())
или thenReturn(any(Foo.class))
, например, в Mockito. Mockito должен точно знать, какой экземпляр возвращать в вызовах-заглушках, и не выберет для вас произвольное возвращаемое значение.
Детали реализации
Сопоставители хранятся (как сопоставители объектов в стиле Hamcrest) в стеке, содержащемся в классе под названием ArgumentMatcherStorage . MockitoCore и Matchers владеют экземпляром ThreadSafeMockingProgress , который статически содержит ThreadLocal, содержащий экземпляры MockingProgress. Именно этот MockingProgressImpl содержит конкретный ArgumentMatcherStorageImpl . Следовательно, состояние mock и matcher является статическим, но последовательно распределено между классами Mockito и Matchers.
Большинство звонков Сличитель только добавить в этот стек, с исключением для matchers , как and
, or
иnot
. Это полностью соответствует (и полагается) порядку оценки Java , который оценивает аргументы слева направо перед вызовом метода:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
Это будет:
- Добавить
anyInt()
в стопку.
- Добавить
gt(10)
в стопку.
- Добавить
lt(20)
в стопку.
- Удалить
gt(10)
и lt(20)
добавить and(gt(10), lt(20))
.
- Вызов
foo.quux(0, 0)
, который (если иное не заглушено) возвращает значение по умолчанию false
. Внутри Mockito помечается quux(int, int)
как самый последний звонок.
- Вызов
when(false)
, который отбрасывает свой аргумент и подготавливает метод-заглушку, quux(int, int)
указанный в 5. Единственными двумя допустимыми состояниями являются длина стека 0 (равенство) или 2 (сопоставители), и в стеке есть два сопоставления (шаги 1 и 4), поэтому Mockito заглушает метод any()
сопоставлением для его первого аргумента и and(gt(10), lt(20))
для его второго аргумента и очищает стек.
Это демонстрирует несколько правил:
Мокито не может отличить quux(anyInt(), 0)
иquux(0, anyInt())
. Оба они выглядят как вызов quux(0, 0)
с одним сопоставлением int в стеке. Следовательно, если вы используете одно сопоставление, вы должны сопоставить все аргументы.
Порядок звонков не просто важен, это то, что заставляет все это работать . Извлечение сопоставлений с переменными обычно не работает, поскольку обычно меняет порядок вызовов. Однако извлечение сопоставлений с методами отлично работает.
int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// The helper method calls the matcher methods in the right order.
Стек меняется достаточно часто, поэтому Mockito не может тщательно его контролировать. Он может проверять стек только тогда, когда вы взаимодействуете с Mockito или имитацией, и должен принимать сопоставители, не зная, используются ли они немедленно или случайно выброшены. Теоретически стек всегда должен быть пустым за пределами вызова when
или verify
, но Mockito не может проверить это автоматически. Вы можете проверить вручную с помощью Mockito.validateMockitoUsage()
.
При вызове when
Mockito фактически вызывает рассматриваемый метод, который генерирует исключение, если вы заглушили метод для генерации исключения (или требуете ненулевых или ненулевых значений).
doReturn
и doAnswer
(т. д.) не вызывают фактический метод и часто являются полезной альтернативой.
Если вы вызвали фиктивный метод в середине создания заглушки (например, чтобы вычислить ответ для eq
сопоставления), Mockito вместо этого проверит длину стека по этому вызову и, скорее всего, потерпит неудачу.
Если вы попытаетесь сделать что-то плохое, например, заглушить / проверить окончательный метод , Mockito вызовет настоящий метод и также оставит дополнительные сопоставители в стеке . final
Вызов методы не может бросить исключение, но вы можете получить InvalidUseOfMatchersException от паразитного matchers , когда вы в следующий раз взаимодействовать с макетом.
Общие проблемы
InvalidUseOfMatchersException :
Убедитесь, что каждый аргумент имеет ровно один вызов сопоставления, если вы вообще используете сопоставители, и что вы не использовали сопоставление вне вызова when
or verify
. Сопоставители никогда не должны использоваться как заглушенные возвращаемые значения или поля / переменные.
Убедитесь, что вы не вызываете макет как часть аргумента сопоставления.
Убедитесь, что вы не пытаетесь заглушить / проверить последний метод с помощью сопоставителя. Это отличный способ оставить сопоставление в стеке, и если ваш последний метод не выдает исключение, это может быть единственный раз, когда вы понимаете, что метод, над которым вы насмехаетесь, является окончательным.
NullPointerException с примитивными аргументами: (Integer) any()
возвращает null, а any(Integer.class)
возвращает 0; это может вызвать a, NullPointerException
если вы ожидаете int
вместо Integer. В любом случае предпочтите anyInt()
, который вернет ноль, а также пропустит этап автоматической упаковки.
NullPointerException или другие исключения: вызовы to when(foo.bar(any())).thenReturn(baz)
будут фактически вызывать foo.bar(null)
, которые вы могли бы заглушить, чтобы генерировать исключение при получении нулевого аргумента. При переключении doReturn(baz).when(foo).bar(any())
на заглушенное поведение пропускается .
Устранение общих неисправностей
Используйте MockitoJUnitRunner или явно вызовите validateMockitoUsage
в своем tearDown
или@After
метод (что бегун сделает за вас автоматически). Это поможет определить, правильно ли вы использовали сопоставители.
В целях отладки добавляйте вызовы прямо validateMockitoUsage
в код. Это сработает, если у вас что-то есть в стеке, что является хорошим предупреждением о плохом симптоме.