How do you unittest exceptions in Dart? - dart

Consider a function that does some exception handling based on the arguments passed:
List range(start, stop) {
if (start >= stop) {
throw new ArgumentError("start must be less than stop");
}
// remainder of function
}
How do I test that the right kind of exception is raised?

In this case, there are various ways to test the exception. To simply test that an unspecific exception is raised:
expect(() => range(5, 5), throwsException);
to test that the right type of exception is raised:
there are several predefined matchers for general purposes like throwsArgumentError, throwsRangeError, throwsUnsupportedError, etc.. for types for which no predefined matcher exists, you can use TypeMatcher<T>.
expect(() => range(5, 2), throwsA(TypeMatcher<IndexError>()));
to ensure that no exception is raised:
expect(() => range(5, 10), returnsNormally);
to test the exception type and exception message:
expect(() => range(5, 3),
throwsA(predicate((e) => e is ArgumentError && e.message == 'start must be less than stop')));
here is another way to do this:
expect(() => range(5, 3),
throwsA(allOf(isArgumentError, predicate((e) => e.message == 'start must be less than stop'))));
(Thanks to Graham Wheeler at Google for the last 2 solutions).

I like this approach:
test('when start > stop', () {
try {
range(5, 3);
} on ArgumentError catch(e) {
expect(e.message, 'start must be less than stop');
return;
}
throw new ExpectException("Expected ArgumentError");
});

As a more elegant solution to #Shailen Tuli's proposal, if you want to expect an error with a specific message, you can use having.
In this situation, you are looking for something like this:
expect(
() => range(5, 3),
throwsA(
isA<ArgumentError>().having(
(error) => error.message, // The feature you want to check.
'message', // The description of the feature.
'start must be less than stop', // The error message.
),
),
);

An exception is checked using throwsA with TypeMatcher.
Note: isInstanceOf is now deprecated.
List range(start, stop) {
if (start >= stop) {
throw new ArgumentError("start must be less than stop");
}
// remainder of function
}
test("check argument error", () {
expect(() => range(1, 2), throwsA(TypeMatcher<ArgumentError>()));
});

While the other answers are definitely valid, APIs like TypeMatcher<Type>() are deprecated now, and you have to use isA<TypeOfException>().
For instance, what was previously,
expect(() => range(5, 2), throwsA(TypeMatcher<IndexError>()));
Will now be
expect(() => range(5, 2), throwsA(isA<IndexError>()));

For simple exception testing, I prefer to use the static method API:
Expect.throws(
// test condition
(){
throw new Exception('code I expect to throw');
},
// exception object inspection
(err) => err is Exception
);

I used the following approach:
First you need to pass in your method as a lambda into the expect function:
expect(() => method(...), ...)
Secondly you need to use throwsA in combination with isInstanceOf.
throwsA makes sure that an exception is thrown, and with isInstanceOf you can check if the correct Exception was thrown.
Example for my unit test:
expect(() => parser.parse(raw), throwsA(isInstanceOf<FailedCRCCheck>()));
Hope this helps.

Related

how do I unit test with multiple expect

The unit test I write below doesn't work when there are 3 expect, how can I rewrite the unit test below:
test('400 bad response', () async {
when(
() => sampleClient.getTransaction(
'1'),
).thenThrow(DioError(
response: Response(
statusCode: 400,
data: badRepsonseJson,
requestOptions: RequestOptions(path: '')),
requestOptions: RequestOptions(path: '')));
final call = sampleService.getTransactionByHash(
'1');
expect(() => call, throwsA(TypeMatcher<SampleException>())); // Expect 1
try {
await sampleService.getTransactionByHash(
'1');
} on SampleException catch (e) {
expect(e.errorCode, badResponse.statusCode); // Expect 2
expect(e.message, badResponse.message); // Expect 3
}
});
final call = sampleService.getTransactionByHash(
'1');
expect(() => call, throwsA(TypeMatcher<SampleException>())); // Expect 1
I would not expect that expect to succeed. Invoking () => call just returns the already computed value of the call variable, which cannot fail.
If you instead expect sampleService.getTransactionByHash('1'); to throw an exception, then you need to invoke that in your callback to expect:
expect(
() => sampleService.getTransactionByHash('1'),
throwsA(TypeMatcher<SampleException>()),
);

rxjs created observable timeout always errors

ok, so now I'm really puzzled. Executing the following code
const created = Rx.Observable.create(observer => {
observer.next(42)
})
const ofd = Rx.Observable.of(42)
const createSub = name => [
val => console.log(`${name} received ${val}`),
error => console.log(`${name} threw ${error.constructor.name}`)
]
created
.timeout(100)
.subscribe(
...createSub('created')
)
ofd
.timeout(100)
.subscribe(
...createSub('ofd')
)
Prints
"created received 42"
"ofd received 42"
"created threw TimeoutError"
I don't understand this at all, why does the created Observable error even though it emits a value but the ofd Observable does not??
Using RxJS 5, problem occurs with 5.0.3 in jsbin.com and 5.4.3 in my app.
(Note: This happens with subjects too, which led me to create this example)
Observable.of is completing the stream right after the value has been emitted.
Observable.create keeps the observable opened. And that's why the timeout is throwing an error.
Replace
const created = Rx.Observable.create(observer => {
observer.next(42)
})
By
const created = Rx.Observable.create(observer => {
observer.next(42);
observer.complete();
})
and there's no error anymore.

Future execution leads to unhandled exception

import 'dart:async';
void main() {
divide(1, 0).then((result) => print('1 / 0 = $result'))
.catchError((error) => print('Error occured during division: $error'));
}
Future<double> divide(int a, b) {
if (b == 0) {
throw new Exception('Division by zero');
}
return new Future.value(a/b);
}
Currently I am learning how to work with futures in Dart and I got stucked on such an easy example. My future throws exception when user tries to perform division by zero. However, .catchError doesn't handle my exception. I got unhandled exception with a stack trace instead. I'm pretty sure that I am missing something obvious but can't understand what exactly.
As I understand, there is another way to handle error:
divide(1, 0).then((result) => print('1 / 0 = $result'),
onError: (error) => print('Error occured during division: $error'));
to use named optional argument - onError. Doing like this still leads to unhandled exception.
I would like to clarify one more thing. Am I right? - The only difference between these 2 approaches is that .catchError() also handles errors thrown by inner futures (futures that are called inside then() method of the outer future) while onError only catches errors thrown by the outer future?
Dmitry
Thank you.
Your error handling didn't work because the error is thrown in the synchronous part of your code. Just because the method returns a future doesn't mean everything in this method is async.
void main() {
try {
divide(1, 0)
.then((result) => print('1 / 0 = $result'));
} catch (error) {
print('Error occured during division: $error');
}
}
If you change your divide function like
Future<double> divide(int a, b) {
return new Future(() {
if (b == 0) {
throw new Exception('Division by zero');
}
return new Future.value(a / b);
});
}
you get an async error and your async error handling works.
An easier way is to use the new async/await
main() async {
try {
await divide(1, 0);
} catch(error){
print('Error occured during division: $error');
}
}
try at DartPad
and another advantage is this works in both cases.

Completer completeError

I'm trying to caught an error from a completer.
Here, my method to decode a token
Future<Map> decode(String token) {
var completer = new Completer();
new Future(() {
List<String> parts = token.split(".");
Map result = {};
try {
result["header"] = JSON.decode(new String.fromCharCodes(crypto.CryptoUtils.base64StringToBytes(parts[0])));
result["payload"] = JSON.decode(new String.fromCharCodes(crypto.CryptoUtils.base64StringToBytes(parts[1])));
} catch(e) {
completer.completeError("Bad token");
return;
}
encode(result["payload"]).then((v_token) {
if (v_token == token) {
completer.complete(result);
} else {
completer.completeError("Bad signature");
}
});
});
return completer.future;
}
}
The call:
var test = new JsonWebToken("topsecret");
test.encode({"field": "ok"}).then((token) {
print(token);
test.decode("bad.jwt.here")
..then((n_tok) => print(n_tok))
..catchError((e) => print(e));
});
And this is the output
dart server.dart
eyJ0eXAiOiJKV1QiLCJhbGciOiJTSEEyNTYifQ==.eyJsdSI6Im9rIn0=.E3TjGiPGSJOIVZFFECJ0OSr0jAWojIfF7MqFNTbFPmI=
Bad token
Unhandled exception:
Uncaught Error: Bad token
#0 _rootHandleUncaughtError.<anonymous closure> (dart:async/zone.dart:820)
#1 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:41)
#2 _asyncRunCallback (dart:async/schedule_microtask.dart:48)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:126)
I don't understand why we tell me that my error is uncaught while it's printed...
I think you misused .. instead of . for chaining future. See https://www.dartlang.org/docs/tutorials/futures/#handling-errors
instead of
test.decode("bad.jwt.here")
..then((n_tok) => print(n_tok))
..catchError((e) => print(e));
can you try
test.decode("bad.jwt.here")
.then((n_tok) => print(n_tok))
.catchError((e) => print(e));
Have a look at this document about how Futures work - https://www.dartlang.org/articles/futures-and-error-handling/.
In particular there is an example which says:
myFunc()
.then((value) {
doSomethingWith(value);
...
throw("some arbitrary error");
})
.catchError(handleError);
If myFunc()’s Future completes with an error, then()’s Future
completes with that error. The error is also handled by catchError().
Regardless of whether the error originated within myFunc() or within
then(), catchError() successfully handles it.
That is consistent with what you're seeing.

Listening to the Stream created from List in Dart

I've modified little bit example from tutorial https://www.dartlang.org/docs/tutorials/streams/ by adding item after subscription:
import 'dart:async';
main() {
var data = new List<int>();
var stream = new Stream.fromIterable(data); // create the stream
// subscribe to the streams events
stream.listen((value) { //
print("Received: $value"); // onData handler
}); //
data.add(1);
}
And after running this program I've got:
Uncaught Error: Concurrent modification during iteration: _GrowableList len:1.
Stack Trace:
#0 ListIterator.moveNext (dart:_collection-dev/iterable.dart:315)
#1 _IterablePendingEvents.handleNext (dart:async/stream_impl.dart:532)
#2 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:661)
#3 _asyncRunCallback (dart:async/schedule_microtask.dart:18)
#4 _createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:11)
#5 _Timer._createTimerHandler._handleTimeout (timer_impl.dart:151)
#6 _Timer._createTimerHandler.<anonymous closure> (timer_impl.dart:166)
#7 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:93)
Putting data.add(1) before adding listener works as expected.
I've checked documentation about Stream and didn't found what I am doing wrong. I was expecting that listener will be fired in best case and just not fired in worst case, but not exception.
Is it expected behavior? If yes, please describe why.
The exception comes from you trying to modify the list while it is iterated over. This is unspecified behaviour in Dart (*), and the used implementation simply chose to throw an exception. While it is obfuscated by the asynchronous stuff happening in Stream.fromIterable, it basically is the same as if you tried to do this:
var data = [1,2,3];
for(var d in data) {
print(d);
data.add(d+10);
}
If you wrapped your data.add in another async call, for example with Timer.run(() => data.add(2)), it would "work". By that, I mean it wouldn't throw an exception.
Received: 2 still would not be printed. The stream will only send the elements that where already in the list at the time new Stream.fromIterable was called. After that, the stream is closed (onDone will be called), and modifications to the original list will not be sent to your listener.
(*) Source: iterator.dart in SDK 1.1.3 -- "If the object iterated over is changed during the iteration, the behavior is unspecified." Why the text on api.dartlang.org is different is beyond me.
EDIT
To answer the question in the comment: One way would be to use a StreamController.
// or new StreamController<int>.broadcast(), if you want to listen to the stream more than once
StreamController s = new StreamController<int>();
// produce periodic errors
new Timer.periodic(new Duration(seconds: 5), (Timer t) {
s.isClosed ? t.cancel() : s.addError("I AM ERROR");
});
// add some elements before subscribing
s.add(6);
s.add(9);
// this will close the stream eventually
new Timer(new Duration(seconds: 20), () => s.close());
// start listening to the stream
s.stream.listen((v) => print(v),
onError: (err) => print("An error occured: $err"),
onDone: () => print("The stream was closed"));
// add another element before the next event loop iteration
Timer.run(() => s.add(4711));
// periodically add an element
new Timer.periodic(new Duration(seconds: 3), (Timer t) {
s.isClosed ? t.cancel() : s.add(0);
});
// one more (will be sent before 4711)
s.add(4);
The List can't be modified while it is iterated over.
You need an iterable that doesn't have this limitation (e.g. custom implementation) for your example.

Resources