Ответ 1
Mockito matchers являются статическими методами и вызовами тех методов, которые поддерживают аргументы во время вызовов when
и verify
.
Соединители Hamcrest (архивная версия) (или совпадения в стиле Hamcrest) являются объектами с объектами универсального назначения, которые реализуют Matcher<T>
и раскрывают метод matches(T)
, который возвращает true, если объект соответствует критериям Matcher. Они предназначены для того, чтобы быть свободными от побочных эффектов и обычно используются в утверждениях, подобных приведенным ниже.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Составы Mockito существуют, отдельно от шаблонов стиля Hamcrest, так что описания совпадающих выражений непосредственно вписываются в вызовы методов: Mockito matchers return T
, где методы совпадения Hamcrest возвращают объекты Matcher ( типа Matcher<T>
).
Соединители Mockito вызываются статическими методами, такими как eq
, any
, gt
и startsWith
на org.mockito.Matchers
и org.mockito.AdditionalMatchers
. Существуют также адаптеры, которые изменились в версиях Mockito:
- Для Mockito 1.x,
Matchers
используются некоторые вызовы (например,intThat
илиargThat
), которые являются макетами Mockito, которые непосредственно принимают параметры Hamcrest в качестве параметров.ArgumentMatcher<T>
extendedorg.hamcrest.Matcher<T>
, который использовался во внутреннем представлении Hamcrest и был базовым классом Matrix для Hamcrest вместо любого типа Mockito-сопряжения. - Для Mockito 2.0+ у Mockito больше нет прямой зависимости от Hamcrest.
Matchers
, выраженные какintThat
илиargThat
wrapArgumentMatcher<T>
объекты, которые больше не реализуютorg.hamcrest.Matcher<T>
, но используются аналогичным образом, Адаптеры Hamcrest, такие какargThat
иintThat
, по-прежнему доступны, но вместо этого переместились вMockitoHamcrest
.
Независимо от того, являются ли шаблоны Hamcrest или просто Hamcrest-стилем, они могут быть адаптированы так:
/* 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
завершает совпадение в стиле Хамкреста и возвращает 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 different
Когда вы вызываете соответствующий элемент типа any
или gt
(больше, чем), Mockito хранит объект-сопряжение, из-за которого Mockito пропускает проверку равенства и применяет ваш выбор. В случае argumentCaptor.capture()
он сохраняет совпадение, которое сохраняет свой аргумент вместо этого для последующего контроля.
Матчи возвращают фиктивные значения, такие как нуль, пустые коллекции или null
. Mockito пытается вернуть безопасное, соответствующее фиктивное значение, например 0 для anyInt()
или any(Integer.class)
или пустой List<String>
для anyListOf(String.class)
. Однако из-за стирания стилей Mockito не имеет информации о типе, чтобы вернуть любое значение, но null
для any()
или argThat(...)
, что может вызвать исключение NullPointerException при попытке "auto-unbox" a null
примитивного значения.
Подходы, такие как eq
и gt
, принимают значения параметров; в идеале эти значения должны вычисляться до начала старта/проверки. Вызов макета в середине издевательства над другим вызовом может помешать выполнению stubbing.
Методы сопоставления не могут использоваться как возвращаемые значения; например, нет способа фразы thenReturn(anyInt())
или thenReturn(any(Foo.class))
в Mockito. Mockito должен точно знать, какой экземпляр будет возвращаться при вызове stubbing, и не выберет для вас произвольное возвращаемое значение.
Сведения о реализации
Матчи хранятся (в виде совпадений объектов типа Hamcrest) в стеке, содержащемся в классе ArgumentMatcherStorage. MockitoCore и Matchers имеют собственный экземпляр ThreadSafeMockingProgress, который статически содержит экземпляры MockingProgress, поддерживающие ThreadLocal. Это MockingProgressImpl, в котором содержится конкретный ArgumentMatcherStorageImpl, Следовательно, состояние mock и matcher является статическим, но поточно-зависимым между классами Mockito и 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
. Internally Mockito отмечаетquux(int, int)
как самый последний вызов. - Вызов
when(false)
, который отбрасывает свой аргумент и готовит метод stubquux(int, int)
, идентифицированный в 5. Только два действительных состояния имеют длину стека 0 (равенство) или 2 (совпадения), и на стек (шаги 1 и 4), поэтому Mockito заглушает метод с помощьюany()
для его первого аргумента иand(gt(10), lt(20))
для своего второго аргумента и очищает стек.
Это демонстрирует несколько правил:
-
Mockito не может определить разницу между
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
(и т.д.) не ссылаются на фактический метод и часто являются полезной альтернативой. -
Если вы вызвали метод mock в середине stubbing (например, для вычисления ответа для
eq
-сервера), Mockito проверил бы длину стека на этот вызов и, скорее всего, не получится. -
Если вы попытаетесь сделать что-то плохое, например stubbing/verifying final method, Mockito вызовет реальный метод, а также уйдет дополнительные сокеты в стеке. Вызов метода
final
не может генерировать исключение, но вы можете получить InvalidUseOfMatchersException от бродячих совпадений, когда вы будете затем взаимодействовать с макетом.
Общие проблемы
-
InvalidUseOfMatchersException:
-
Убедитесь, что каждый отдельный аргумент имеет ровно один вызов-сопряжение, если вы вообще используете совпадения, и что вы не использовали совпадение вне вызова
when
илиverify
. Матчи никогда не должны использоваться в качестве заглубленных возвращаемых значений или полей/переменных. -
Убедитесь, что вы не вызываете макет как часть предоставления аргумента сопряжения.
-
Убедитесь, что вы не пытаетесь заглушить/проверить окончательный метод с помощью соединителя. Это отличный способ оставить помощника в стеке, и если ваш последний метод не сделает исключение, это может быть единственный раз, когда вы поймете, что метод, который вы издеваетесь, является окончательным.
-
-
NullPointerException с примитивными аргументами:
(Integer) any()
возвращает значение null, аany(Integer.class)
возвращает 0; это может привести кNullPointerException
, если вы ожидаетеint
вместо Integer. В любом случае, предпочитайтеanyInt()
, который будет возвращать ноль, а также пропустить шаг автоматического бокса. -
Исключение NullPointerException или другие исключения: Вызовы
when(foo.bar(any())).thenReturn(baz)
на самом деле вызовутfoo.bar(null)
, который, возможно, запустил исключение при получении нулевого аргумента. Переключение наdoReturn(baz).when(foo).bar(any())
пропускает зашитое поведение.
Общее устранение неполадок
-
Используйте MockitoJUnitRunner или явно вызывайте
validateMockitoUsage
в вашем методеtearDown
или@After
(который бегун сделает для вас автоматически). Это поможет определить, были ли вы неправильно использованы сокеты. -
В целях отладки добавьте вызовы
validateMockitoUsage
в свой код напрямую. Это произойдет, если у вас есть что-нибудь в стеке, что является хорошим предупреждением о плохом симптом.