fake_async
fake_async copied to clipboard
FakeAsync hangs when operating on a Stream
This test:
test('Should interrupt on timeout', () {
//given
var controller = StreamController<Uint8List>();
const expectedError = 'No response';
//when
result() {
final comleter = Completer<bool>();
controller.stream.timeout(
Duration(seconds: 5),
onTimeout: (sink) {
print('on timeout!');
sink.close();
},
).listen((event) {
}, onDone: () {
print('onDone');
comleter.completeError(Exception(expectedError));
});
return comleter.future;
}
//then
expect(result, throwsA(predicate((e) => e is Exception && e.toString() == 'Exception: $expectedError')));
});
passes as expected after 5 seconds. But when I apply FakeAsync:
test('Should interrupt on timeout', () {
fakeAsync((async) {
//given
var controller = StreamController<Uint8List>();
const expectedError = 'No response';
//when
result() {
final comleter = Completer<bool>();
controller.stream.timeout(
Duration(seconds: 5),
onTimeout: (sink) {
print('on timeout!');
sink.close();
},
).listen((event) {
}, onDone: () {
print('onDone');
comleter.completeError(Exception(expectedError));
});
return comleter.future;
}
//then
expect(result, throwsA(predicate((e) => e is Exception && e.toString() == 'Exception: $expectedError')));
async.elapse(Duration(seconds: 6));
});
});
it displays:
on timeout!
onDone
as expeceted, but then test hangs and is interrupted after 30sec by timeout:
00:31 +0 -1: Should interrupt on timeout [E]
TimeoutException after 0:00:30.000000: Test timed out after 30 seconds. See https://pub.dev/packages/test#timeouts
dart:isolate _RawReceivePort._handleMessage
Try moving the final comleter = Completer<bool>(); outside of the fakeAsync zone.
It's hard to reason about fakeAsync, but my experience is that you should never rely on anything created inside the fake-async zone to happen, or not happen, unless you are actively elapsing time. Don't trust that anything can cross the zone boundary by itself, because that requires something driving the internal progress.
So, if possible, I try to make sure elapsing is done by code running outside of the fake-async zone (because otherwise we can get a deadlock when nothing elapses to the microtask which should run the async.elapse(...), and try to push results outside of the zone as well, to check them there. (But I don't have a pattern that I know will always work.)
thanks @lrhn, this code:
void main() {
test('Should interrupt on timeout', () {
final completer = Completer<bool>();
fakeAsync((async) {
//given
var controller = StreamController<Uint8List>();
const expectedError = 'No response';
//when
result() {
controller.stream.timeout(
Duration(seconds: 5),
onTimeout: (sink) {
print('on timeout!');
sink.close();
},
).listen((event) {
}, onDone: () {
print('onDone');
completer.completeError(Exception(expectedError));
});
return completer.future;
}
//then
expect(result, throwsA(predicate((e) => e is Exception && e.toString() == 'Exception: $expectedError')));
async.elapse(Duration(seconds: 6));
});
});
}
indeed works - although I have no idea why, because for me:
- Code inside
fakeAsync((async) {was executed, because'onDone'was printed on the console - it is the
completer.completeError(Exception(expectedError));who should end the future so what's creation offinal completer = Completer<bool>();has to do with it?
Kind regards