Как работают составители Mockito?

Сопоставители аргументов Mockito (такие как any, argThat, eq, same и ArgumentCaptor.capture()) ведут себя совершенно иначе, чем сопоставители Hamcrest.

  • Сопоставители Mockito часто вызывают InvalidUseOfMatchersException, даже в коде, который выполняется задолго после того, как были использованы сопоставления.

  • Сопоставители Mockito обязаны соблюдать странные правила, например требовать использования сопоставителей Mockito для всех аргументов, если один аргумент в данном методе использует сопоставление.

  • Сопоставители Mockito могут вызвать исключение NullPointerException при переопределении Answer или при использовании (Integer) any() и т. д.

  • Рефакторинг кода с помощью сопоставителей Mockito определенным образом может привести к исключениям и непредвиденному поведению, а также может привести к сбою.

Почему сопоставители Mockito спроектированы так и как они реализованы?

101 голос | спросил Jeff Bowman 3 AMpThu, 03 Apr 2014 00:37:16 +040037Thursday 2014, 00:37:16

2 ответа


0

Сопоставители Mockito являются статическими методами и вызовами тех методов, которые заменяют аргументы во время вызовов when и verify

Сопоставители Hamcrest (архивная версия) (или сопоставители в стиле Hamcrest) являются экземплярами объектов общего назначения без сохранения состояния, которые реализуют Matcher<T> и предоставляют метод ---- +: = 3 =: + ----, который возвращает true, если объект соответствует критериям Matcher. Предполагается, что они не имеют побочных эффектов и обычно используются в утверждениях, таких как приведенное ниже.

matches(T)

Сопоставители Mockito существуют отдельно от сопоставителей в стиле Hamcrest, , так что описания соответствующих выражений вписываются непосредственно в вызовы методов : Сопоставители Mockito возвращают /* Mockito */ verify(foo).setPowerLevel(gt(9000)); /* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000))); , где методы сопоставления Hamcrest возвращают объекты Matcher (типа T)

Сопоставители Mockito вызываются статическими методами, такими как Matcher<T>, eq, any и gt в startsWith и org.mockito.Matchers , Также есть адаптеры, которые изменились в разных версиях Mockito:

  • Для Mockito 1.x org.mockito.AdditionalMatchers добавил некоторые вызовы (например, Matchers или intThat) - сопоставители Mockito, которые непосредственно принимают сопоставители Hamcrest в качестве параметров. argThat extended ArgumentMatcher<T>, который использовался во внутреннем представлении Hamcrest и был базовым классом сопоставителя Hamcrest вместо любого сопоставителя Mockito.
  • Для Mockito 2.0+ Mockito больше не имеет прямой зависимости от Hamcrest. org.hamcrest.Matcher<T> звонки, обозначенные как Matchers или intThat wrap argThat объекты, которые больше не реализуют ArgumentMatcher<T>, но используются аналогичным образом. Адаптеры Hamcrest, такие как org.hamcrest.Matcher<T> и argThat еще доступны, но перешли на страницу intThat .

Независимо от того, являются ли сопоставители Hamcrest или просто в стиле Hamcrest, их можно адаптировать следующим образом:

MockitoHamcrest

В приведенном выше утверждении: /* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */ verify(foo).setPowerLevel(intThat(is(greaterThan(9000)))); - это метод, который принимает foo.setPowerLevel. int возвращает is(greaterThan(9000)), который не будет t работает как аргумент Matcher<Integer>. Сопоставитель Mockito setPowerLevel оборачивает этот сопоставитель в стиле Хамкреста и возвращает intThat так что может появиться в качестве аргумента; Сопоставители Mockito, такие как int, обернут все это выражение в один вызов, как в первой строке примера кода.

Что сопоставляют /возвращают

gt(9000)

Когда не используются средства сопоставления аргументов, Mockito записывает значения аргументов и сравнивает их со своими методами when(foo.quux(3, 5)).thenReturn(true); .

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 (больше чем), Mockito хранит объект соответствия, которыйзаставляет Mockito пропустить проверку на равенство и применить выбранный вами матч. В случае gt он сохраняет сопоставитель, который сохраняет свой аргумент вместо этого для последующей проверки.

Сопоставители возвращают фиктивные значения , такие как ноль, пустые коллекции или argumentCaptor.capture(). Mockito пытается вернуть безопасное подходящее фиктивное значение, например 0 для null или anyInt() или пустое any(Integer.class) для List<String>. Однако из-за стирания типа в Mockito не хватает информации о типе, чтобы вернуть какое-либо значение, но anyListOf(String.class) для null или any(), что может вызвать исключение NullPointerException при попытке "автоматической разблокировки" a ---- +: = 49 =: + ---- примитивное значение.

Сопоставители, такие как argThat(...) и null принимать значения параметров; в идеале, эти значения должны быть вычислены до начала проверки /проверки. Вызывая макет во время насмешки, другой вызов может помешать работе с заглушкой.

Методы Matcher нельзя использовать в качестве возвращаемых значений; нет способа фразы eq или gt В Мокито, например. Mockito должен точно знать, какой экземпляр возвращать в вызовах-заглушках, и не выберет для вас произвольное возвращаемое значение.

Подробности реализации

Сопоставители хранятся (как сопоставители объектов в стиле Hamcrest) в стеке, содержащемся в классе с именем ArgumentMatcherStorage . Каждый из MockitoCore и Matchers имеет ThreadSafeMockingProgress экземпляр, который статически содержит ThreadLocal, содержащий экземпляры MockingProgress. Это MockingProgressImpl который содержит конкретный ArgumentMatcherStorageImpl . Следовательно, состояние mock и matcher является статическим, но согласованно с потоками между классами Mockito и Matchers.

Большинство вызовов совпадений добавляют только в этот стек, за исключением таких совпадений, как thenReturn(anyInt()), thenReturn(any(Foo.class)) и and . Это полностью соответствует (и опирается) на порядок оценки Java , который оценивает аргументы слева направо перед вызовом метод:

or

Это будет:

  1. Добавьте not в стек.
  2. Добавьте when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true); [6] [5] [1] [4] [2] [3] в стек.
  3. Добавьте anyInt() в стек.
  4. Удалите gt(10) и lt(20) и добавить gt(10).
  5. Вызовите lt(20), который (если не указано иное) возвращает значение по умолчанию and(gt(10), lt(20)). Внутренне Mockito помечает foo.quux(0, 0) как самый последний вызов.
  6. Вызовите false, который отбрасывает свой аргумент и подготавливает метод-заглушку quux(int, int) указано в 5. Только два допустимых состояния имеют длину стека 0 (равенство) или 2 (совпадения), и в стеке есть два сопоставления (шаги 1 и 4), поэтому Mockitoзаглушает метод с помощью сопоставителя when(false) для его первого аргумента и quux(int, int) для второго аргумента и очищает стек.

Это демонстрирует несколько правил:

  • Mockito не может определить разницу между any() и and(gt(10), lt(20)). Они оба выглядят как вызов quux(anyInt(), 0) с одним совпадением int в стеке. Следовательно, если вы используете одно сопоставление, вы должны сопоставить все аргументы.

  • Порядок вызовов не просто важен, это то, что заставляет все это работать . Извлечение соответствий в переменные обычно не работает, потому что это обычно меняет порядок вызовов. Однако извлечение методов сопоставления для методов прекрасно работает.

    quux(0, anyInt())
  • Стек меняется достаточно часто, поэтому Mockito не может тщательно его контролировать. Он может проверять стек только тогда, когда вы взаимодействуете с Mockito или имитатором, и должен принимать совпадения, не зная, используются ли они сразу или случайно. Теоретически, стек всегда должен быть пустым вне вызова quux(0, 0) или 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 не может проверить это автоматически. Вы можете проверить вручную с помощью when.

  • При вызове verify Mockito фактически вызывает соответствующий метод, который выдает исключение, если вы заглушил метод, чтобы вызвать исключение (или потребовать ненулевые или ненулевые значения). Mockito.validateMockitoUsage() и when (и т. д.) сделать not вызывают фактический метод и часто являются полезной альтернативой.

  • Если вы вызвали фиктивный метод в середине заглушки (например, чтобы вычислить ответ для сопоставителя doReturn) Вместо этого Mockito проверит длину стека по этому вызову и, скорее всего, потерпит неудачу.

  • Если вы попытаетесь сделать что-то плохое, например, заглушая /проверяя последний метод , Mockito вызовет реальный метод , а также оставит дополнительные соответствия в стеке . Вызов метода doAnswer может не вызвать исключение, но вы можете получить InvalidUseOfMatchersException из случайных совпадений при следующем взаимодействии с макетом.

Распространенные проблемы

  • InvalidUseOfMatchersException

    • Убедитесь, что у каждого аргумента есть ровно один вызов совпадения, если вы вообще используете совпадения и что вы не использовали сопоставление вне eq или final. Сопоставители никогда не должны использоваться в качестве заглушаемых возвращаемых значений или полей /переменных.

    • Убедитесь, что вы не вызываете макет как часть предоставления аргумента соответствия.

    • Убедитесь, что вы не пытаетесь заглушить /проверить последний метод с помощью сопоставителя. Это отличный способ оставить сопоставление в стеке, и если ваш последний метод не выдает исключение, это может быть единственный раз, когда вы поймете, что метод, над которым вы работаете, является окончательным.

  • NullPointerException с примитивными аргументами: when возвращает ноль, а verify возвращает 0; это может вызвать (Integer) any(), если вы ожидаете any(Integer.class) вместо целого числа. В любом случае, предпочтите NullPointerException, который вернет ноль и пропустит этап автобокса.

  • NullPointerException или другие исключения: вызовы int фактически вызовут вызов anyInt(), что вы можетеошарашен, чтобы выдать исключение при получении нулевого аргумента. Переключение на when(foo.bar(any())).thenReturn(baz) пропускает поведение с заглушкой .

Устранение общих неполадок

  • Используйте MockitoJUnitRunner или явным образом вызовите foo.bar(null) в вашем doReturn(baz).when(foo).bar(any()) или validateMockitoUsage (который бегун сделает для вас автоматически). Это поможет определить, правильно ли вы использовали совпадения.

  • В целях отладки добавьте вызовы к tearDown в своем коде напрямую. Это выкинет, если у вас есть что-нибудь в стеке, что является хорошим предупреждением о плохом симптоме.

ответил Jeff Bowman 3 AMpThu, 03 Apr 2014 00:37:16 +040037Thursday 2014, 00:37:16
0

Просто небольшое дополнение к отличному ответу Джеффа Боумена, когда я нашел этот вопрос, когда искал решение одной из моих собственных проблем:

Если вызов метода соответствует нескольким обученным вызовам when одного макета, порядок when вызовы важны и должны быть от самых широких до самых конкретных. Начиная с одного из примеров Джеффа:

when(foo.quux(anyInt(), anyInt())).thenReturn(true);
when(foo.quux(anyInt(), eq(5))).thenReturn(false);

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

foo.quux(3 /*any int*/, 8 /*any other int than 5*/) //returns true
foo.quux(2 /*any int*/, 5) //returns false

Если вы инвертируете вызовы when, то результатом всегда будет true.

ответил tibtof 5 42015vEurope/Moscow11bEurope/MoscowThu, 05 Nov 2015 17:58:56 +0300 2015, 17:58:56

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132