Add asyncExpectThrows<T>() to async_helper.

This lets you test that a callback returns a Future that completes to
a given error. Like Expect.throws(), but async.

At first, I added support for this directly to Expect.throws(), but I
think it's better to minimize the amount of dynamic logic going on in
the language test framework.

I was worried about having to duplicate all of the Expect.throws___()
convenience functions but now that we have generic methods, those
functions aren't that much more convenient.

Change-Id: I8b288945611fa16f8d27056f3cf79181fc22d256
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97881
Reviewed-by: William Hesse <whesse@google.com>
This commit is contained in:
Bob Nystrom 2019-03-27 18:26:15 +00:00
parent 994f535e03
commit ffee99d79b
2 changed files with 72 additions and 16 deletions

View file

@ -25,6 +25,8 @@ library async_helper;
import 'dart:async';
import 'package:expect/expect.dart';
bool _initialized = false;
int _asyncLevel = 0;
@ -87,3 +89,52 @@ Future<void> asyncTest(f()) {
asyncStart();
return f().then(asyncSuccess);
}
/// Calls [f] and verifies that it throws a `T`.
///
/// The optional [check] function can provide additional validation that the
/// correct object is being thrown. For example, to check the content of the
/// thrown object you could write this:
///
/// asyncExpectThrows<MyException>(myThrowingFunction,
/// (e) => e.myMessage.contains("WARNING"));
///
/// If `f` fails an expectation (i.e., throws an [ExpectException]), that
/// exception is not caught by [asyncExpectThrows]. The test is still considered
/// failing.
void asyncExpectThrows<T>(Future<void> f(),
[bool check(T error), String reason]) {
var type = "";
if (T != dynamic && T != Object) type = "<$T>";
var header = "asyncExpectThrows$type(${reason ?? ''}):";
// TODO(rnystrom): It might useful to validate that T is not bound to
// ExpectException since that won't work.
if (f is! Function()) {
// Only throws from executing the function body should count as throwing.
// The failure to even call `f` should throw outside the try/catch.
Expect.testError("$header Function not callable with zero arguments.");
}
var result = f();
if (result is! Future) {
Expect.testError("$header Function did not return a Future.");
}
asyncStart();
result.then((_) {
throw ExpectException("$header Did not throw.");
}).catchError((error, stack) {
// A test failure doesn't count as throwing.
if (error is ExpectException) throw error;
if (error is! T || (check != null && !check(error))) {
// Throws something unexpected.
throw ExpectException(
"$header Unexpected '${Error.safeToString(error)}'\n$stack");
}
asyncEnd();
});
}

View file

@ -4,7 +4,7 @@
// SharedOptions=--enable-experiment=control-flow-collections,spread-collections
import "package:async_helper/async_helper.dart";
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'utils.dart';
@ -250,25 +250,30 @@ Future<void> testKeyOrder() async {
Future<void> testRuntimeErrors() async {
// Cast variable.
dynamic nonStream = 3;
Expect.throwsTypeError(() => <int>[await for (int i in nonStream) 1]);
Expect.throwsTypeError(() => <int, int>{await for (int i in nonStream) 1: 1});
Expect.throwsTypeError(() => <int>{await for (int i in nonStream) 1});
asyncExpectThrows<TypeError>(
() async => <int>[await for (int i in nonStream) 1]);
asyncExpectThrows<TypeError>(
() async => <int, int>{await for (int i in nonStream) 1: 1});
asyncExpectThrows<TypeError>(
() async => <int>{await for (int i in nonStream) 1});
// Null stream.
Stream<int> nullStream = null;
Expect.throwsNoSuchMethodError(
() => <int>[await for (var i in nullStream) 1]);
Expect.throwsNoSuchMethodError(
() => <int, int>{await for (var i in nullStream) 1: 1});
Expect.throwsNoSuchMethodError(
() => <int>{await for (var i in nullStream) 1});
asyncExpectThrows<NoSuchMethodError>(
() async => <int>[await for (var i in nullStream) 1]);
asyncExpectThrows<NoSuchMethodError>(
() async => <int, int>{await for (var i in nullStream) 1: 1});
asyncExpectThrows<NoSuchMethodError>(
() async => <int>{await for (var i in nullStream) 1});
// Wrong element type.
dynamic nonInt = "string";
Expect.throwsTypeError(() => <int>[await for (var i in stream([1])) nonInt]);
Expect.throwsTypeError(
() => <int, int>{await for (var i in stream([1])) nonInt: 1});
Expect.throwsTypeError(
() => <int, int>{await for (var i in stream([1])) 1: nonInt});
Expect.throwsTypeError(() => <int>{await for (var i in stream([1])) nonInt});
asyncExpectThrows<TypeError>(
() async => <int>[await for (var i in stream([1])) nonInt]);
asyncExpectThrows<TypeError>(
() async => <int, int>{await for (var i in stream([1])) nonInt: 1});
asyncExpectThrows<TypeError>(
() async => <int, int>{await for (var i in stream([1])) 1: nonInt});
asyncExpectThrows<TypeError>(
() async => <int>{await for (var i in stream([1])) nonInt});
}