How to test a stream in Dart
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
.
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();
}
});
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;
}
}