Search code examples
dartstreamflutter-test

Dart async* stream breaks randomly in unit tests


A simple use-case, I get data lists form the server in chunks. I want to convert that to a stream. Before that I wanted to test if things are working in a unit test.

Environment

  • Flutter (Channel stable, 1.22.4, on Linux)
  • Dart SDK version: 2.10.4 (stable)

Below is the simplified code combined in one file.

import 'package:flutter_test/flutter_test.dart';

class BeanList<T> {
  final List<T> list;
  final int count;

  BeanList({this.list, this.count});
}

abstract class PageProvider<T> {
  Future<BeanList<T>> provide();
}

class IntPageProvider implements PageProvider<int> {
  final size = 5;
  final count = 9;
  int offset = 0;

  List<int> _generateList(int startFrom, int size) {
    List<int> list = [];
    for (int i = startFrom; i < startFrom + size; i++) {
      list.add(i);
    }
    return list;
  }

  @override
  Future<BeanList<int>> provide() async {
    final list = _generateList(offset, size);
    print('provide: $list');
    offset += size;
    return BeanList<int>(list: list, count: count);
  }
}

Stream<T> itemStream<T>(PageProvider<T> provider) async* {
  int total;
  int sent = 0;
  bool done = false;

  while (true) {
    final BeanList<T> beanList = await provider.provide();
    final list = beanList.list;
    final count = beanList.count;
    total = beanList.count;

    print('Got count:$count, list:$list');
    for (final item in list) {
      print('yield $item');
      yield item;
      sent++;
      done = sent == total;
      if (done) break;
    }
    if (done) break;
  }
}

void main() {
  test('Randomly breaking stream', () {
    itemStream(IntPageProvider()).listen(print);
  });
}

Expected output I should get the numbers 0 through 8, which I sometimes do, but most of the times the test stops at any random location.


Solution

  • Thanks Christopher Moore for your time, I have found the problem.

    The problem is with how I'm consuming the stream within the test.

    Currently it's being done like so.

    test('Randomly breaking stream', () {
        itemStream(IntPageProvider()).listen(print);
    });
    

    Stream.listen() runs asynchronously within the test, with nothing to make the test wait for the stream to be consumed fully. So the test ends and all the process within the test stops abruptly, and hence the random stopping point.

    However if we make a few changes to the test.

    test('Test waiting for the stream consumption', () async {
        await itemStream(IntPageProvider()).listen(print).asFuture();
    });
    

    This makes sure that the test will not end until the stream has been fully consumed.

    I hope this will help anyone who falls into the same issue.