-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathasync_helper.dart
169 lines (153 loc) · 6.08 KB
/
async_helper.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// This library is used for testing asynchronous tests.
/// If a test is asynchronous, it needs to notify the testing driver
/// about this (otherwise tests may get reported as passing after main()
/// finished even if the asynchronous operations fail).
///
/// This library provides the following methods
/// - `asyncStart()`: Needs to be called before an asynchronous operation is
/// scheduled.
/// - `asyncEnd()`: Needs to be called as soon as the asynchronous operation
/// ended.
/// - `asyncSuccess(_)`: Variant of asyncEnd useful together with Future.then.
/// - `asyncTest(f)`: Helper method that wraps a computation that returns a
/// `Future` with matching calls to `asyncStart()` and
/// `asyncSuccess(_)`.
/// - `asyncExpectThrows<E>(f)`: Checks that the `Future` `f` completes
/// with an error of type `E`. Calls `asyncStart()`
/// first and `asyncEnd()` when the future completes with
/// the expected error.
/// - `asyncExpectThrowsWhen<E>(condition, f)`:
/// Checks that the `Future` `f` completes with an error of
/// type `E` if `condition` is true, and does not fail
/// if `condition` is false. Calls `asyncStart()`
/// first and `asyncEnd()` when the future completes with
/// the expected error, or with no error if the condition
/// is false.
///
/// After every `asyncStart()` called is matched with a corresponding
/// `asyncEnd()` or `asyncSuccess(_)` call, the testing driver will be notified
/// that the tests is done.
library async_helper;
import 'dart:async';
import 'package:expect/expect.dart';
bool _initialized = false;
int _asyncLevel = 0;
Exception _buildException(String msg) {
return Exception('Fatal: $msg. This is most likely a bug in your test.');
}
/// Call this method before an asynchronous test is created.
///
/// If [count] is provided, expect [count] [asyncEnd] calls instead of just one.
void asyncStart([int count = 1]) {
if (count <= 0) return;
if (_initialized && _asyncLevel == 0) {
throw _buildException('asyncStart() was called even though we are done '
'with testing.');
}
if (!_initialized) {
print('unittest-suite-wait-for-done');
_initialized = true;
}
_asyncLevel += count;
}
/// Call this after an asynchronous test has ended successfully.
void asyncEnd() {
if (_asyncLevel <= 0) {
if (!_initialized) {
throw _buildException('asyncEnd() was called before asyncStart().');
} else {
throw _buildException('asyncEnd() was called more often than '
'asyncStart().');
}
}
_asyncLevel--;
if (_asyncLevel == 0) {
print('unittest-suite-success');
}
}
/// Call this after an asynchronous test has ended successfully.
///
/// This is a helper for calling [asyncEnd] as a callback.
/// This method intentionally has a signature that matches `Future.then` as a
/// convenience for calling [asyncEnd] when a `Future` completes without error,
/// like this:
/// ```dart
/// asyncStart();
/// Future result = test();
/// result.then(asyncSuccess);
/// ```
void asyncSuccess(void _) {
asyncEnd();
}
/// Helper method for performing asynchronous tests involving `Future`.
///
/// The function [test] must return a `Future` which completes without error
/// when the test is successful.
Future<void> asyncTest(Future<void> Function() test) {
asyncStart();
return test().then(asyncSuccess);
}
/// Verifies that the asynchronous [result] throws a [T].
///
/// Fails if [result] completes with a value, or it completes with
/// an error which is not a [T].
///
/// Returns the accepted thrown object.
/// For example, to check the content of the thrown object,
/// you could write this:
/// ```
/// var e = await asyncExpectThrows<MyException>(asyncExpression)
/// Expect.isTrue(e.myMessage.contains("WARNING"));
/// ```
/// If `result` completes with an [ExpectException] error from another
/// failed test expectation, that error cannot be caught and accepted.
Future<T> asyncExpectThrows<T extends Object>(Future<void> result,
[String reason = ""]) {
// Delay computing the header text until the test has failed.
// The header computation uses complicated language features,
// and language tests should avoid doing complicated things
// until after the actual test has had a chance to succeed.
String header() {
// Handle null being passed in from legacy code
// while also avoiding producing an unnecessary null check warning here.
if ((reason as dynamic) == null) reason = "";
// Only include the type in the message if it's not `Object` or a top type.
var type = Object() is! T ? "<$T>" : "";
return "asyncExpectThrows$type($reason):";
}
// Unsound null-safety check.
if ((result as dynamic) == null) {
Expect.testError("${header()} result Future must not be null.");
}
// TODO(rnystrom): It might useful to validate that T is not bound to
// ExpectException since that won't work.
asyncStart();
return result.then<T>((_) {
throw ExpectException("${header()} Did not throw.");
}, onError: (error, stack) {
// A test failure doesn't count as throwing. Rethrow it.
if (error is ExpectException) throw error;
if (error is! T) {
// Throws something unexpected.
throw ExpectException(
"${header()} Unexpected '${Error.safeToString(error)}'\n$stack");
}
asyncEnd();
return error;
});
}
/// Checks that the asynchronous [result] throws a [T] if and only if
/// [condition] is `true`.
///
/// When [condition] is `false`, [result] is expected to complete without
/// errors.
Future<T?> asyncExpectThrowsWhen<T extends Object>(
bool condition, Future<void> result,
[String reason = ""]) {
return condition
? asyncExpectThrows<T>(result, reason)
: result.then<T?>((_) => null);
}