Use fake_async over waiting for timers with delays (dart-lang/stream_transform#166)
On the web the timers may have some variance.
Replace all usages of `waitForTimer` utility to use `fakeAsync`.
Separate out the call to `Stream.listen` from `setUp` so it
happens in the fake async zone.
diff --git a/pkgs/stream_transform/pubspec.yaml b/pkgs/stream_transform/pubspec.yaml
index 915dc10..4d1ffbe 100644
--- a/pkgs/stream_transform/pubspec.yaml
+++ b/pkgs/stream_transform/pubspec.yaml
@@ -8,5 +8,6 @@
dev_dependencies:
async: ^2.5.0
+ fake_async: ^1.3.0
lints: ^2.0.0
test: ^1.16.0
diff --git a/pkgs/stream_transform/test/audit_test.dart b/pkgs/stream_transform/test/audit_test.dart
index 907d873..e2f8133 100644
--- a/pkgs/stream_transform/test/audit_test.dart
+++ b/pkgs/stream_transform/test/audit_test.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import 'package:fake_async/fake_async.dart';
import 'package:stream_transform/stream_transform.dart';
import 'package:test/test.dart';
@@ -21,7 +22,7 @@
late StreamSubscription<int> subscription;
group('audit', () {
- setUp(() async {
+ setUp(() {
valuesCanceled = false;
values = createController(streamType)
..onCancel = () {
@@ -31,73 +32,106 @@
errors = [];
isDone = false;
transformed = values.stream.audit(const Duration(milliseconds: 6));
+ });
+
+ void listen() {
subscription = transformed
.listen(emittedValues.add, onError: errors.add, onDone: () {
isDone = true;
});
- });
+ }
test('cancels values', () async {
+ listen();
await subscription.cancel();
expect(valuesCanceled, true);
});
- test('swallows values that come faster than duration', () async {
- values
- ..add(1)
- ..add(2);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [2]);
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2]);
+ });
});
- test('outputs multiple values spaced further than duration', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
});
- test('waits for pending value to close', () async {
- values.add(1);
- await values.close();
- expect(isDone, false);
- await waitForTimer(5);
- expect(isDone, true);
+ test('waits for pending value to close', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..close();
+ expect(isDone, false);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(isDone, true);
+ });
});
- test('closes output if there are no pending values', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await values.close();
- expect(isDone, false);
- await waitForTimer(5);
- expect(isDone, true);
+ test('closes output if there are no pending values', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values
+ ..add(2)
+ ..close();
+ expect(isDone, false);
+ expect(emittedValues, [1]);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(isDone, true);
+ expect(emittedValues, [1, 2]);
+ });
});
test('does not starve output if many values come closer than duration',
- () async {
- values.add(1);
- await Future.delayed(const Duration(milliseconds: 4));
- values.add(2);
- await Future.delayed(const Duration(milliseconds: 4));
- values.add(3);
- await waitForTimer(6);
- expect(emittedValues, [2, 3]);
+ () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(3);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2, 3]);
+ });
});
if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = [];
- transformed.listen(otherValues.add);
- values
- ..add(1)
- ..add(2);
- await waitForTimer(5);
- expect(emittedValues, [2]);
- expect(otherValues, [2]);
+ test('multiple listeners all get the values', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(2);
+ var otherValues = [];
+ transformed.listen(otherValues.add);
+ values.add(3);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(4);
+ async.elapse(const Duration(milliseconds: 3));
+ values
+ ..add(5)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [3, 5]);
+ expect(otherValues, [3, 5]);
+ });
});
}
});
diff --git a/pkgs/stream_transform/test/debounce_test.dart b/pkgs/stream_transform/test/debounce_test.dart
index f9c97eb..6b9775f 100644
--- a/pkgs/stream_transform/test/debounce_test.dart
+++ b/pkgs/stream_transform/test/debounce_test.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import 'package:fake_async/fake_async.dart';
import 'package:stream_transform/stream_transform.dart';
import 'package:test/test.dart';
@@ -31,63 +32,83 @@
errors = [];
isDone = false;
transformed = values.stream.debounce(const Duration(milliseconds: 5));
+ });
+
+ void listen() {
subscription = transformed
.listen(emittedValues.add, onError: errors.add, onDone: () {
isDone = true;
});
- });
+ }
test('cancels values', () async {
+ listen();
await subscription.cancel();
expect(valuesCanceled, true);
});
- test('swallows values that come faster than duration', () async {
- values
- ..add(1)
- ..add(2);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [2]);
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2]);
+ });
});
- test('outputs multiple values spaced further than duration', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
});
- test('waits for pending value to close', () async {
- values.add(1);
- await waitForTimer(5);
- await values.close();
- await Future(() {});
- expect(isDone, true);
+ test('waits for pending value to close', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.close();
+ async.flushMicrotasks();
+ expect(isDone, true);
+ });
});
- test('closes output if there are no pending values', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await Future(() {});
- await values.close();
- expect(isDone, false);
- await waitForTimer(5);
- expect(isDone, true);
+ test('closes output if there are no pending values', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values
+ ..add(2)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, false);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(isDone, true);
+ });
});
if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = [];
- transformed.listen(otherValues.add);
- values
- ..add(1)
- ..add(2);
- await waitForTimer(5);
- expect(emittedValues, [2]);
- expect(otherValues, [2]);
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = [];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2]);
+ expect(otherValues, [2]);
+ });
});
}
});
@@ -103,13 +124,17 @@
emittedValues = [];
isDone = false;
transformed = values.stream.debounce(const Duration(milliseconds: 5),
- leading: true, trailing: false)
- ..listen(emittedValues.add, onDone: () {
- isDone = true;
- });
+ leading: true, trailing: false);
});
+ void listen() {
+ transformed.listen(emittedValues.add, onDone: () {
+ isDone = true;
+ });
+ }
+
test('swallows values that come faster than duration', () async {
+ listen();
values
..add(1)
..add(2);
@@ -117,29 +142,36 @@
expect(emittedValues, [1]);
});
- test('outputs multiple values spaced further than duration', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
});
if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = [];
- transformed.listen(otherValues.add);
- values
- ..add(1)
- ..add(2);
- await waitForTimer(5);
- expect(emittedValues, [1]);
- expect(otherValues, [1]);
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = [];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1]);
+ expect(otherValues, [1]);
+ });
});
}
test('closes output immediately if not waiting for trailing value',
() async {
+ listen();
values.add(1);
await values.close();
expect(isDone, true);
@@ -155,38 +187,49 @@
values = createController(streamType);
emittedValues = [];
transformed = values.stream.debounce(const Duration(milliseconds: 5),
- leading: true, trailing: true)
- ..listen(emittedValues.add);
+ leading: true, trailing: true);
+ });
+ void listen() {
+ transformed.listen(emittedValues.add);
+ }
+
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 3]);
+ });
});
- test('swallows values that come faster than duration', () async {
- values
- ..add(1)
- ..add(2)
- ..add(3);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [1, 3]);
- });
-
- test('outputs multiple values spaced further than duration', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
});
if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = [];
- transformed.listen(otherValues.add);
- values
- ..add(1)
- ..add(2);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
- expect(otherValues, [1, 2]);
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = [];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ expect(otherValues, [1, 2]);
+ });
});
}
});
@@ -201,49 +244,60 @@
values = createController(streamType);
emittedValues = [];
errors = [];
- transformed = values.stream
- .debounceBuffer(const Duration(milliseconds: 5))
- ..listen(emittedValues.add, onError: errors.add);
+ transformed =
+ values.stream.debounceBuffer(const Duration(milliseconds: 5));
});
+ void listen() {
+ transformed.listen(emittedValues.add, onError: errors.add);
+ }
- test('Emits all values as a list', () async {
- values
- ..add(1)
- ..add(2);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [
- [1, 2]
- ]);
- });
-
- test('separate lists for multiple values spaced further than duration',
- () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await waitForTimer(5);
- expect(emittedValues, [
- [1],
- [2]
- ]);
- });
-
- if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = [];
- transformed.listen(otherValues.add);
+ test('Emits all values as a list', () {
+ fakeAsync((async) {
+ listen();
values
..add(1)
- ..add(2);
- await waitForTimer(5);
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
expect(emittedValues, [
[1, 2]
]);
- expect(otherValues, [
- [1, 2]
+ });
+ });
+
+ test('separate lists for multiple values spaced further than duration',
+ () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [
+ [1],
+ [2]
]);
});
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = [];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [
+ [1, 2]
+ ]);
+ expect(otherValues, [
+ [1, 2]
+ ]);
+ });
+ });
}
});
});
diff --git a/pkgs/stream_transform/test/throttle_test.dart b/pkgs/stream_transform/test/throttle_test.dart
index 22bf5b3..07f607a 100644
--- a/pkgs/stream_transform/test/throttle_test.dart
+++ b/pkgs/stream_transform/test/throttle_test.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import 'package:fake_async/fake_async.dart';
import 'package:stream_transform/stream_transform.dart';
import 'package:test/test.dart';
@@ -29,49 +30,68 @@
emittedValues = [];
isDone = false;
transformed = values.stream.throttle(const Duration(milliseconds: 5));
+ });
+
+ void listen() {
subscription = transformed.listen(emittedValues.add, onDone: () {
isDone = true;
});
- });
+ }
test('cancels values', () async {
+ listen();
await subscription.cancel();
expect(valuesCanceled, true);
});
- test('swallows values that come faster than duration', () async {
- values
- ..add(1)
- ..add(2);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [1]);
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1]);
+ });
});
- test('outputs multiple values spaced further than duration', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ async.elapse(const Duration(milliseconds: 6));
+ });
});
- test('closes output immediately', () async {
- values.add(1);
- await waitForTimer(5);
- values.add(2);
- await values.close();
- expect(isDone, true);
+ test('closes output immediately', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values
+ ..add(2)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, true);
+ });
});
if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = <int>[];
- transformed.listen(otherValues.add);
- values.add(1);
- await Future(() {});
- expect(emittedValues, [1]);
- expect(otherValues, [1]);
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values.add(1);
+ async.flushMicrotasks();
+ expect(emittedValues, [1]);
+ expect(otherValues, [1]);
+ });
});
}
});
@@ -87,65 +107,84 @@
isDone = false;
transformed = values.stream
.throttle(const Duration(milliseconds: 5), trailing: true);
+ });
+ void listen() {
subscription = transformed.listen(emittedValues.add, onDone: () {
isDone = true;
});
+ }
+
+ test('emits both first and last in a period', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
});
- test('emits both first and last in a period', () async {
- values
- ..add(1)
- ..add(2);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
- });
-
- test('swallows values that are not the latest in a period', () async {
- values
- ..add(1)
- ..add(2)
- ..add(3);
- await values.close();
- await waitForTimer(5);
- expect(emittedValues, [1, 3]);
+ test('swallows values that are not the latest in a period', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 3]);
+ });
});
test('waits to output the last value even if the stream closes',
() async {
- values
- ..add(1)
- ..add(2);
- await values.close();
- await Future(() {});
- expect(isDone, false);
- expect(emittedValues, [1],
- reason: 'Should not be emitted until after duration');
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
- expect(isDone, true);
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, false);
+ expect(emittedValues, [1],
+ reason: 'Should not be emitted until after duration');
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ expect(isDone, true);
+ async.elapse(const Duration(milliseconds: 6));
+ });
});
- test('closes immediately if there is no pending value', () async {
- values.add(1);
- await values.close();
- await Future(() {});
- expect(isDone, true);
+ test('closes immediately if there is no pending value', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, true);
+ });
});
if (streamType == 'broadcast') {
- test('multiple listeners all get values', () async {
- var otherValues = <int>[];
- transformed.listen(otherValues.add);
- values
- ..add(1)
- ..add(2);
- await Future(() {});
- expect(emittedValues, [1]);
- expect(otherValues, [1]);
- await waitForTimer(5);
- expect(emittedValues, [1, 2]);
- expect(otherValues, [1, 2]);
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.flushMicrotasks();
+ expect(emittedValues, [1]);
+ expect(otherValues, [1]);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ expect(otherValues, [1, 2]);
+ });
});
}
});
diff --git a/pkgs/stream_transform/test/utils.dart b/pkgs/stream_transform/test/utils.dart
index b6196d6..42d9613 100644
--- a/pkgs/stream_transform/test/utils.dart
+++ b/pkgs/stream_transform/test/utils.dart
@@ -4,12 +4,6 @@
import 'dart:async';
-/// Cycle the event loop to ensure timers are started, then wait for a delay
-/// longer than [milliseconds] to allow for the timer to fire.
-Future<void> waitForTimer(int milliseconds) =>
- Future(() {/* ensure Timer is started*/})
- .then((_) => Future.delayed(Duration(milliseconds: milliseconds + 1)));
-
StreamController<T> createController<T>(String streamType) {
switch (streamType) {
case 'single subscription':