Рассмотрим следующий код, который необходимо протестировать.
void run() {
_activityRepo.activityUpdateStream.listen((token) async {
await _userRepo.updateToken(token: token);
});
}
где _activityRepo.activityUpdateStream
— это Stream<String>
, которое генерирует String
события.
Цель здесь — проверить, что функция updateToken
вызывается каждый раз, когда _activityRepo.activityUpdateStream
генерирует новое событие. Модульный тест в настоящее время выглядит так:
test('should update notification token when refreshed', () async {
const fakePushToken = 'token';
final fakeStream = StreamController<String>();
when(() => liveActivityRepo.activityUpdateStream).thenAnswer((_) => fakeStream.stream);
useCase.run();
fakeStream.add(fakeUpdate);
verify(() => userRepo.updateLiveActivityToken(token: fakePushToken)).called(1);
});
Этот тест не пройден, потому что verify
вызывается перед функцией updateToken
внутри слушателя потока. Как мне переписать этот тест, чтобы я мог убедиться, что прослушиватель потока полностью выполнен, прежде чем запускать шаг проверки в тесте?
Этот модульный тест терпит неудачу, потому что, как вы объяснили, verify
выполняется до асинхронных операций прослушивателя потока.
Эти асинхронные операции могут занять некоторое время и, следовательно, зависят от некоторых таймеров (включая те, которые обрабатывают тайм-ауты). Подумайте об удаленных вызовах API. Эти операции помещаются в так называемую очередь событий, потому что их завершение зависит от некоторых внешних событий.
Но это могут быть просто отложенные операции, которые выполняются быстро, называемые микрозадачами. Они могут сбивать с толку, потому что выглядят как синхронные операции, и мы можем ожидать, что они будут выполняться синхронно. Они помещаются в так называемую очередь микрозадач.
Я не знаю, какую асинхронную задачу выполняет ваша updateToken
, поэтому я даю вам оба решения.
Я сделал этот пример:
import 'package:fake_async/fake_async.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
main() {
test("Sync with timers", () {
int n = 0;
var stream = Stream.fromIterable([1, 2, 3, 4, 5]);
stream.listen((value) async {
await Future.delayed(Duration(seconds: value));
n = value;
debugPrint(value.toString());
});
expect(n, 0);
});
test("Sync with microtasks", () {
int n = 0;
var stream = Stream.fromIterable([1, 2, 3, 4, 5]);
stream.listen((value) async {
n = value;
debugPrint(value.toString());
});
expect(n, 0);
});
test("Fake async with timers", () {
fakeAsync((fa) {
int n = 0;
var stream = Stream.fromIterable([1, 2, 3, 4, 5]);
stream.listen((value) async {
await Future.delayed(Duration(seconds: value));
n = value;
debugPrint(value.toString());
});
fa.flushTimers();
expect(n, 5);
});
});
test("Fake async with microtasks", () {
fakeAsync((fa) {
int n = 0;
var stream = Stream.fromIterable([1, 2, 3, 4, 5]);
stream.listen((value) async {
n = value;
debugPrint(value.toString());
});
fa.flushMicrotasks();
expect(n, 5);
});
});
}
Первые два теста показывают, что мы ожидаем от синхронного модульного теста, подобного тому, что вы сделали. n
не будет увеличиваться до вызова expect
. Второй тест самый удивительный, потому что даже если наша «асинхронная» задача на самом деле синхронная (она просто присваивает значение и печатает его), она запускается только после операторов синхронизации, включая expect
.
Используя пакет fake_async
, вы можете управлять очередью событий и очередью микрозадач. Ваш модульный тест вложен в вызов fakeAsync, и, вызвав flushTimers
или flushMicrotasks
, вы можете выполнить все асинхронные операции прослушивателя потока.
Обратите внимание, что тесты fakeAsync потерпят неудачу, если вы поменяете местами вызовы flushTimers
и flushMicrotasks
, что показывает, какой из них вам нужно вызвать, зависит от конкретной природы асинхронных задач.
Пожалуйста! На самом деле мне нужно было лучше понять эту часть Flutter/Dart, и это был хороший шанс углубиться.