Как выполнить модульное тестирование асинхронного вызова функции внутри прослушивателя потока

Рассмотрим следующий код, который необходимо протестировать.

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 внутри слушателя потока. Как мне переписать этот тест, чтобы я мог убедиться, что прослушиватель потока полностью выполнен, прежде чем запускать шаг проверки в тесте?

1
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Mauro Vanetti 14.04.2023 15:02

Другие вопросы по теме