Матчі-відповідники - це статичні методи та виклики до тих методів, які є аргументами під час викликів до when
та verify
.
Матчі Hamcrest (архівована версія) (або відповідники стилю Hamcrest) - це об'єкти без об'єкта, без загального призначення, які реалізують Matcher<T>
та розкривають метод, matches(T)
який повертає істину, якщо об'єкт відповідає критеріям Матчера. Вони призначені для позбавлення від побічних ефектів, і, як правило, використовуються в таких твердженнях, як наведене нижче.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Матчі-відповідники існують окремо від відповідних стилів Hamcrest, так що описи відповідних виразів вписуються безпосередньо у виклики методів : Матчі-макіто повертаються T
туди, де методики Матчреста повертають об'єкти Матчер (типу 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 matcher замість будь-якого типу Mockito matcher.
- Для Mockito 2.0+ Mockito більше не має прямої залежності від Hamcrest.
Matchers
називає фразовані як intThat
або argThat
обгортання ArgumentMatcher<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
обгортає відповідник стилю Hamcrest і повертає int
так, що це може з'явитися як аргумент; Матчі-відповідники, як-от, перетворили 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 , який статично містить екземпляри MockingProgress , які містять ThreadLocal. Саме цей MockingProgressImpl містить конкретний ArgumentMatcherStorageImpl . Отже, стан макетів та матчерів є статичним, але послідовно розподіляється між класами 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
. Мокіто внутрішньо позначається quux(int, int)
як останній дзвінок.
- Call
when(false)
, який відкидає свій аргумент і готується до методу stub, 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 matcher на стеку. Отже, якщо ви використовуєте один матч, ви повинні відповідати всім аргументам.
Замовлення на дзвінки не просто важливе, саме це і робить це все можливим . Витяг матчів до змінних, як правило, не працює, тому що зазвичай змінюється порядок викликів. Однак видобуток матчерів до методів працює чудово.
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
або verify
викликом. Матчі ніколи не повинні використовуватися як заглушені зворотні значення або поля / змінні.
Переконайтеся, що ви не викликаєте макет як частину надання аргументу відповідника.
Переконайтеся, що ви не намагаєтеся заглушити / перевірити остаточний метод за допомогою відповідника. Це прекрасний спосіб залишити матч на стеці, і якщо ваш остаточний метод не викидає виняток, це може бути єдиний раз, коли ви усвідомлюєте, що метод, з якого ви знущаєтесь, остаточний.
NullPointerException з примітивними аргументами: (Integer) any()
повертає null, а any(Integer.class)
повертає 0; це може спричинити, NullPointerException
якщо ви очікуєте int
замість цілого числа. У будь-якому випадку віддайте перевагу anyInt()
, яка поверне нуль, а також пропустить крок автобоксу.
NullPointerException або інші винятки: Виклики when(foo.bar(any())).thenReturn(baz)
насправді дзвонять foo.bar(null)
, які, можливо, вам доведеться кинути виняток при отриманні аргументу нуля. Перемикання на doReturn(baz).when(foo).bar(any())
пропуск стрибкованої поведінки .
Загальне усунення несправностей
Використовуйте MockitoJUnitRunner або явно зателефонуйте validateMockitoUsage
у свій метод tearDown
чи @After
метод (який бігун зробить для вас автоматично). Це допоможе визначити, що ви неправомірно використовували відповідники.
З метою налагодження додайте дзвінки validateMockitoUsage
у свій код безпосередньо. Це кине, якщо у вас є що-небудь на стеку, що є хорошим попередженням про поганий симптом.