How to test an exception from a future How to test an exception from a future dart dart

How to test an exception from a future


Use try-catch

The most reliable approach is to use a try-catch block to explicitly catch the exception and ensure the method has finished running.

try {    await methodWhichThrows();    fail("exception not thrown");} catch (e) {    expect(e, new isInstanceOf<...>());    // more expect statements can go here}

This approach also has the advantage that additional checks on the value of the exception can be performed.

Expect with throwsA only works as the last statement

Using expect by itself only works if it is the last statement in the test. There is no control over when the method will throw the exception, so there can be race conditions with the statements (including subsequent calls to expect), if they assume the exception has already been thrown.

expect(methodWhichThrows(), throwsA(new isInstanceOf<...>())); // unreliable unless last

It can be used, but you have to very careful to remember which situations it works in and which it doesn't. So it is safer to stick with the try-catch approach rather than using different approaches for different situations.

Demonstration

The following complete example demonstrates the effect of race conditions on the two approaches:

import 'dart:async';import 'package:test/test.dart';//----------------------------------------------------------------/// This approach to expecting exceptions is reliable.///Future reliableApproach(int luck) async {  expect(await setValueAndReturnsHalf(42), equals(21));  expect(state, equals(Evenness.isEven));  try {    await setValueAndReturnsHalf(3);    fail("exception not thrown");  } catch (e) {    expect(e, new isInstanceOf<ArgumentError>());  }  // Expect value to be odd after execption is thrown.  await shortDelay(luck); // in my experience there's no such thing called luck  expect(state, equals(Evenness.isOdd));}//----------------------------------------------------------------/// This approach to expecting exceptions is unreliable.///Future unreliableApproach(int luck) async {  expect(await setValueAndReturnsHalf(42), equals(21));  expect(state, equals(Evenness.isEven));  expect(setValueAndReturnsHalf(3), throwsA(new isInstanceOf<ArgumentError>()));  // Expect value to be odd after execption is thrown.  await shortDelay(luck); // luck determines if the race condition is triggered  expect(state, equals(Evenness.isOdd));}//----------------------------------------------------------------enum Evenness { isEven, isOdd, inLimbo }int value = 0;Evenness state = Evenness.isEven;/// Sets the [value] and [state].////// If the [newValue] is even, [state] is set to [Evenness.isEven] and half of it/// is returned as the Future's value.////// If the [newValue] is odd, [state] is set to [Evenness.isOdd] and an exception/// is thrown.////// To simulate race conditions, this method takes 2 seconds before it starts/// processing and 4 seconds to succeed or throw an exception. While it is/// processing, the [state] is set to [Evenness.inLimbo].///Future<int> setValueAndReturnsHalf(int newValue) async {  await shortDelay(2);  state = Evenness.inLimbo;  await shortDelay(2);  value = newValue;  if (newValue % 2 != 0) {    state = Evenness.isOdd;    throw new ArgumentError.value(newValue, "value", "is not an even number");  } else {    state = Evenness.isEven;    return value ~/ 2;  }}/// Delays used to simulate processing and race conditions.///Future shortDelay(int seconds) {  var c = new Completer();  new Timer(new Duration(seconds: seconds), () => c.complete());  return c.future;}/// Examples of the reliable and unreliable approaches.///void main() {  test("Correct operation when exception is not thrown", () async {    expect(await setValueAndReturnsHalf(42), equals(21));    expect(value, equals(42));  });  group("Reliable approach:", () {    test("works when there is bad luck", () async {      // 1 second = bad luck, future returning function not started processing yet      await reliableApproach(1);    });    test("works when there is more bad luck", () async {      // 3 second = bad luck, future returning function still processing      await reliableApproach(3);    });    test("works when there is good luck", () async {      // 5 seconds = good luck, future returning function definitely finished      await reliableApproach(5);    });  });  group("Unreliable approach:", () {    test("race condition encountered by bad luck", () async {      // 1 second = bad luck, future returning function not started processing yet      await unreliableApproach(1);    });    test("race condition encountered by more bad luck", () async {      // 3 second = bad luck, future returning function still processing      await unreliableApproach(3);    });    test("race condition avoided by good luck", () async {      // 5 seconds = good luck, future returning function definitely finished      await unreliableApproach(5);    });  });}


This way it works:

import 'package:test/test.dart';import 'dart:async';void main() {  test( "test2", ()  { // with or without `async`    expect(throws(), throwsA(const TypeMatcher<FormatException>()));  });}Future throws () async {  throw new FormatException("hello");}

Basically just remove await. The test framework can deal with futures no matter if they succeed or fail.


There are multiple ways to test errors coming from Future. Gunter's answer will work if "throws without async" method is throwing some exception. Below sample will handle exception coming from future methods.

import 'package:test/test.dart';import 'dart:async';void main() {    test("test with Zone", () {        runZoned(() {            throws();        }, onError: expectAsync((e, s) {            expect(e, new isInstanceOf<FormatException>());        }));    });    test('test with future catch error', () {        throws().catchError(expectAsync((e) {            expect(e, new isInstanceOf<FormatException>());        }));    });}Future throws() async{    Completer completer = new Completer();    completer.complete(new Future(() => throw new FormatException("hello")));    return completer.future;}