Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead агентства продуктовой разработки Amiga. В предыдущих статьях, мы разобрали, как использовать библиотеку Mocktail для техник mocking и stubbing в Unit-тестах. Сегодня погрузимся глубже в ее изучение. Оригинал перевода по ссылке. Все выпуски на моей странице. Поехали!
Чтобы не пропускать новые выпуски, подписывайтесь на наш авторский телеграм-канал Flutter. Много. Там мы делимся кейсами, личным опытом, полезными плагинами и библиотеками.
Функции «verify»
Unit-тесты часто проверяют результат выполнения функции, основанный на входных данных. А что делать, если функция не возвращает результат (void методы)? Как тогда их протестировать?
Mocktail предоставляет функцию verify
для проверки, был ли вызван метод и сколько раз это произошло.
Предположим, есть функция login
, которая после успешной авторизации перенаправляет пользователя на домашний экран.
class LoginViewModel { final Navigator navigator; LoginViewModel({ required this.navigator, }); void login(String email) { if (email.isNotEmpty) { navigator.push('home'); } } }
Проверим эту функцию в 2 тест кейсах:
-
Если email пустой, то у навигатора не будет вызвана функция
push
. -
Если email не пустой, то у навигатора будет вызвана функция
push
только один раз.
Для первого случая используем функцию verifyNever()
, чтобы удостовериться, что навигатор не вызывает функцию push
.
test( 'navigator.push should not be called when the email is empty', () { // Arrange String email = ''; // Act loginViewModel.login(email); // Assert verifyNever(() => mockNavigator.push('home')); }, );
Для второго случая используем функцию verify().called(1), чтобы проверить, что навигатор вызвал push всего один раз.
verify(() => mockNavigator.push('home')).called(1);
Предположим, если обновить код функции login
, чтобы после успешной авторизации он переводил пользователя не только на домашний экран, но и пушил экран профиля.
void login(String email) { if (email.isNotEmpty) { navigator.push('home'); navigator.push('profile'); } }
Тогда нужно обновить и тест тоже.
verify(() => mockNavigator.push('home')).called(2);
Но при запуске теста получаем ошибку.
Была ли функция push
вызвана всего один раз? Нет, один раз была вызвана функция push
с аргументом ‘home’, но сама функция ‘home’ вызывается дважды. Есть 2 способа исправления такой ошибки:
Первый способ — вызывать функцию verify
дважды, вместо одного раза:
verify(() => mockNavigator.push('home')).called(1); verify(() => mockNavigator.push('profile')).called(1);
Второй способ — передать any()
функции verify
:
verify(() => mockNavigator.push(any())).called(2);
Функция «any»
Функция any
используется для сравнения любого значения с определенным типом данных. На примере выше, any()
может соответствовать и ‘home’, и ‘profile’.
В двух исправлениях, упомянутых выше, если передать определенное значение, такое как ‘home’ или ‘profile’, то тест будет более строгим и намного более точным, по сравнению с any()
. Это происходит потому, что any()
соответствует любому значению, которое может быть ‘home’, ‘profile’, или любое другое, например, ‘login’ или ‘register’.
Для того, чтобы сделать any()
строже, нужно использовать ее с параметром that
. Параметр that
используется для соответствия любого аргумента, который удовлетворяет определенным условиям.
verify( () => mockNavigator.push( any(that: isA<String>() .having((e) => e.isNotEmpty, 'isNotEmpty', true)), ), ).called(2);
Например, поменяем функцию push
для использования именованных параметров:
void push({ required String screenName, }) {}
После этого модифицируем тест:
verify(() => mockNavigator.push(screenName: any())).called(2);
Когда запустим тест снова, обнаружим ошибку:
Это потому, что когда используется any()
, как аргумент для именованных параметров, то нужно передать имя этого параметра в параметре named
функции any()
. Конкретно здесь, необходимо вызвать any(named: 'screenName')
вместо any()
.
verify(() => mockNavigator.push( screenName: any(named: 'screenName'), )).called(2);
Обратите внимание, что нужно использовать функцию any()
как аргумент к методам when
для stubbing или verify
для верификации. В противном случае, получится ошибка:
Invalid argument(s): The "any" argument matcher is used outside of method stubbing (via `when`) or verification (via `verify` or `untilCalled`).
Например, когда это используется в функции login
, оно вызовет ошибку.
loginViewModel.login(any());
Функция «captureAny»
Если нужно проверить, что функция push
была вызвана сначала с аргументом «home», а потом с «profile». В таком случае, если вызывать функцию verify
дважды, она будет неспособна проверить, так как нет ничего, что гарантирует, что push('home')
была вызвана раньше, чем push('profile')
.
// BAD verify(() => mockNavigator.push('home')).called(1); verify(() => mockNavigator.push('profile')).called(1);
Тогда нужно использовать функцию captureAny
.
test('navigator.push should be called with the correct argument when the email is not empty', () { // Arrange String email = 'ntminh@gmail.com'; // Act loginViewModel.login(email); // Verify that the navigator.push method is called with the correct argument final capturedArguments = verify(() => mockNavigator.push(captureAny())).captured; expect(capturedArguments, ['home', 'profile']); expect(capturedArguments[0], 'home'); expect(capturedArguments[1], 'profile'); });
Функция captureAny
используется для захвата всех значений аргументов. После захвата, можно проверить были ли переданы функции нужные аргументы. Также как функция any()
, у captureAny()
есть 2 параметра — named
и that
, которые по своему функционалу повторяют такие же, как в any()
.
Функция «registerFallbackValue»
Предположим, что теперь обновили функцию push
так, что вместо передачи String, будет передаваться кастомный тип Screen
.
class Navigator { void push(Screen name) {} } class Screen { final String name; Screen(this.name); }
Далее в тесте будет использоваться функция any()
, чтобы представить любое значение Screen
.
verify(() => mockNavigator.push(any())).called(2);
Когда тест запустится, возникнет ошибка.
Есть указания, как исправить ее при помощи функции registerFallbackValue
и объекта с типом Screen
.
setUpAll(() { registerFallbackValue(Screen('login')); });
Почему функция any()
кидает ошибку, когда заменяем ей кастомный тип, и что за функция registerFallbackValue
?
Когда используем функции any()
и captureAny()
, Mocktail нужно зарегистрировать значения fallback по умолчанию. Для примитивных типов данных Mocktail сделает это автоматически. Однако, для кастомных типов нужно использовать registerFallbackValue()
, чтобы зарегистрировать значения fallback. Если этого не сделать, то это приведет к ошибке, упомянутой выше.
Нужно вызвать registerFallbackValue()
один раз для каждого типа, чтобы зарегистрировать его fallback значение. Оно будет использоваться во всех тестах. Поэтому лучшим местом для вызова функции registerFallbackValue()
является setUpAll()
.
На этом всё! В следующей статье познакомимся с новой техникой, похожей на Mocking — Faking.
Больше про кроссплатформенную разработку в телеграм-канале Flutter. Много.
ссылка на оригинал статьи https://habr.com/ru/articles/833390/
Добавить комментарий