How to test a stream in Dart
Asked Answered
P

3

17

How to test a stream in dart? I have this code:

test('words are reading sequentially correct', () {
  WordTrackerInterface wordTracker = WordTracker.byContent('0 1 2');
  wordTracker.setWordsCountPerChunk(1);
  var stream = wordTracker.nextWordStringStream();

  expect(
      stream,
      emitsInOrder(List<Word>.generate(
          6, (i) => i > 2 ? Word('') : Word(i.toString()))));

  for (int i = 0; i < 6; i++) {
    wordTracker.nextWord();
  }
});

I need to test that the member data Word::content which is a String is equal to that provided in the emitsInOrder.

Something like the following for stream:

expect(
    stream,
    emitsInOrder(List<Word>.generate(
        6, (i) => i > 2 ? Word('') : Word(i.toString()))),
    expect((Word actual, Word expected) {
  return actual.content == expected.content;
}));
Penitentiary answered 21/8, 2018 at 21:30 Comment(0)
P
10

After reading the source code in dart files and reading on the internet, I found the solution: I needed to create a custom Matcher. I tested the following code on my laptop and by referencing other files from my application like 'WordTracker', the code ran as expected.

test('words are reading sequentially correct', () {
    WordTrackerInterface wordTracker = WordTracker.byContent('0 1 2');
    wordTracker.setWordsCountPerChunk(1);
    var stream = wordTracker.nextWordStringStream();

    expect(stream, 
      emitsInOrder(List<Word>.generate(6, (i) => i > 2 ? Word('') : Word(i.toString())).map<WordMatcher>(
        (Word value) => WordMatcher(value))));

    for (int i = 0; i < 6; i++) {
      wordTracker.nextWord();
    }
  });


class WordMatcher extends Matcher {
  Word expected;
  Word actual;
  WordMatcher(this.expected);

  @override
  Description describe(Description description) {
    return description.add("has expected word content = '${expected.content}'");
  }

  @override
  Description describeMismatch(
    dynamic item,
    Description mismatchDescription,
    Map<dynamic, dynamic> matchState,
    bool verbose
  ) {
    return mismatchDescription.add("has actual emitted word content = '${matchState['actual'].content}'");
  }

  @override
  bool matches(actual, Map matchState) {
    this.actual = actual;
    matchState['actual'] = actual is Word ? actual : Word('unknown');
    return (actual as Word).content == expected.content;
  }
}
Penitentiary answered 22/8, 2018 at 17:6 Comment(0)
G
14

Try using async/await and expectLater

test('words are reading sequentially correct', () async {
  WordTrackerInterface wordTracker = WordTracker.byContent('0 1 2');
  wordTracker.setWordsCountPerChunk(1);
  var stream = wordTracker.nextWordStringStream();

  await expectLater(
      stream,
      emitsInOrder(List<Word>.generate(
          6, (i) => i > 2 ? Word('') : Word(i.toString()))));

  for (int i = 0; i < 6; i++) {
    wordTracker.nextWord();
  }
});
Gapes answered 22/8, 2018 at 2:17 Comment(3)
Hi @Kevin, thanks for your response, but what you've provided is not answering my question. I want inside the emitsInOrder to check if what is emitted from Stream which is Word (object) and that the field 'content' which is of type 'String' is the same 'String' that is provided in the List<Word>.generatePenitentiary
Something like this (this code is hypothetical): expect(stream, emitsInOrder(List<Word>.generate( 6, (i) => i > 2 ? Word('') : Word(i.toString()))), (Word actual, Word expected) { return actual.content == expected.content; });Penitentiary
How can I test stream created with broadcast stream controller? dart final _eventsController = StreamController<DomainEvent>.broadcast(); Stream<DomainEvent> get events => _eventsController.stream; Delicate
P
10

After reading the source code in dart files and reading on the internet, I found the solution: I needed to create a custom Matcher. I tested the following code on my laptop and by referencing other files from my application like 'WordTracker', the code ran as expected.

test('words are reading sequentially correct', () {
    WordTrackerInterface wordTracker = WordTracker.byContent('0 1 2');
    wordTracker.setWordsCountPerChunk(1);
    var stream = wordTracker.nextWordStringStream();

    expect(stream, 
      emitsInOrder(List<Word>.generate(6, (i) => i > 2 ? Word('') : Word(i.toString())).map<WordMatcher>(
        (Word value) => WordMatcher(value))));

    for (int i = 0; i < 6; i++) {
      wordTracker.nextWord();
    }
  });


class WordMatcher extends Matcher {
  Word expected;
  Word actual;
  WordMatcher(this.expected);

  @override
  Description describe(Description description) {
    return description.add("has expected word content = '${expected.content}'");
  }

  @override
  Description describeMismatch(
    dynamic item,
    Description mismatchDescription,
    Map<dynamic, dynamic> matchState,
    bool verbose
  ) {
    return mismatchDescription.add("has actual emitted word content = '${matchState['actual'].content}'");
  }

  @override
  bool matches(actual, Map matchState) {
    this.actual = actual;
    matchState['actual'] = actual is Word ? actual : Word('unknown');
    return (actual as Word).content == expected.content;
  }
}
Penitentiary answered 22/8, 2018 at 17:6 Comment(0)
V
9

If your stream is emitting objects with properties you'd like to test, expectAsync1 can help:

List<Record> expectedRecords = [record1, record2, record3];
int i = 0;
recordStream.listen(
    expectAsync1<void,Record>(
    (record) {
      expect(record.name, expectedRecords[i].name);
      i++;
    }, 
    max: -1)
);

In the above example, expectAsync1 encloses an anonymous function:

    (record) {
      expect(record.name, expectedRecords[i].name);
      i++;
    }

This gets run each time a Record is emitted by the Stream recordStream

The 1 in expectAsync1 is the number of arguments your enclosed function will take. Most often, this would be 1. (record) is the one argument above.

For the above example, expectAsync1 has (optional) type arguments: <void,Record> The 2nd type argument Record tells the enclosed function that the stream item emitted is of Record type, allowing me to use properties like record.name without casting.

The 1st type argument is the return type of your enclosed function. I used void cause the enclosed function isn't returning anything, it's just running an expect Matcher and iterating a counter, which is used to iterate through the list of Record I'm expecting to see (i.e. expectedRecords) in that order.

Max argument

You'll notice the max: -1 below the enclosed function. That's an optional but important argument for expectAsync1 specifying the number of stream items/events we're expecting.

This defaults to 1 if max is not given and your test will fail if more than 1 event is emitted.

The error will be Callback called more times than expected (1).

In the example above I used -1 wich means unlimited events can be emitted/tested. You can specify a non-zero number if you want to test you get exactly that many items/events from your stream, else the test will fail. I could have used max: 3 for my example above.

RxDart

If you're using RxDart BehaviorSubject remember the most recent stream event is emitted upon listen. So in your test, when you start listening / using expectAsync1 there will be an immediate call of the enclosed function with the most recent event.

ReplaySubject will emit all previous stream events upon listen.

Varsity answered 9/9, 2020 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.