86

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?

K Walrath
  • 300
  • 3
  • 10
Shailen Tuli
  • 13,815
  • 5
  • 40
  • 51

7 Answers7

178

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).

Dirk Horsten
  • 3,753
  • 4
  • 20
  • 37
Shailen Tuli
  • 13,815
  • 5
  • 40
  • 51
  • 9
    I was doing `expect(range(5, 3), throwsArgumentError)`, but the exception was not catched inside the expect. The first argument to expect must be an anonymous function that will eventually throw when called. Your answer helped me to find that stupid error, thanks! – fgiraldeau Dec 11 '17 at 00:14
  • meanwhile `new isInstanceOf()` is deprecated. TypeMatcher should be used instead: `throwsA(TypeMatcher())` for instance – 5422m4n Nov 25 '18 at 15:46
  • 8
    It seems using TypeMatcher no longer works. What works is `throwsA(isA())` – Matthias Schippling Jan 17 '20 at 10:30
  • 2
    This is definitely the best answer but I wonder why the syntax has to be so verbose for such a simple task.. –  Apr 15 '20 at 20:58
  • You cannot do without the closure for the test, but if you need this many time consider creating a custom matcher for more terse code. – Ber Apr 01 '21 at 09:47
  • This works with constructors as well which have arguments – James Hurford May 11 '23 at 14:17
19

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");  
});
Shannon -jj Behrens
  • 4,910
  • 2
  • 19
  • 24
  • 1
    It's a bit more verbose than the other options, but it's fairly readable, and it doesn't assume you have the entire unittest library memorized ;) – Shannon -jj Behrens Nov 08 '12 at 22:44
17

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.
    ),
  ),
);
Hugo Passos
  • 7,719
  • 2
  • 35
  • 52
7

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>()));
}); 
attdona
  • 17,196
  • 7
  • 49
  • 60
6

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>()));
Brett Sutton
  • 3,900
  • 2
  • 28
  • 53
Dinanjanan
  • 399
  • 2
  • 11
2

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
);
John Evans
  • 6,858
  • 4
  • 28
  • 26
2

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.

LostSoul
  • 331
  • 2
  • 11