Testing exceptions in Dart

When writing tests around code that will throw an exception, how can Dart/Mockito(or anything else) avoid throwing a real exception? For example, these tests should both pass and detect the thrown exception - but Dart throws a real exception in the 1st test so only 'It receives a Todo' passes.

void main() {
  test('It throws an exception', () async {
    final client = MockClient();
    when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'))).thenAnswer((_) async => http.Response('', 404));
    expect(await fetchTodo(client, 1), throwsException);
  });

  test('It receives a Todo', () async {
    final client = MockClient();
    final jsonString = '''
    {
      "id": 1,
      "userId": 1,
      "title": "test",
      "completed": false
    }
    ''';
    when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/2'))).thenAnswer((_) async => http.Response(jsonString, 200));
    expect(await fetchTodo(client, 2), isA<Todo>());
  });
}

and the mocked get method(based on mockito's generated code - I get the same results when using @GenerateMocks([http.Client]) in my test file.

class MockClient extends Mock implements http.Client {
  Future<http.Response> get(Uri url, {Map<String, String>? headers}) {
    return super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}), returnValue: Future.value(http.Response('', 200))) as Future<http.Response>;
  }
}
class Todo {
  int id;
  int userId;
  String title;
  bool completed;

  Todo(this.id, this.userId, this.title, this.completed);
}

Future<Todo> fetchTodo(http.Client client, int id) async {
  final response = await client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/$id'));

  if(response.statusCode == 200) {
    return Todo(1, 1, 'Test', true);
  }else {
    throw Exception('Failed to fetch resource');
  }
}

Test run report:

00:00 +0: It throws an exception
00:00 +0 -1: It throws an exception [E]
  Exception: Failed to fetch resource
  test/test.dart 49:5  fetchTodo
  
00:00 +0 -1: It receives a Todo
00:00 +1 -1: Some tests failed.

1 answer

  • answered 2021-02-23 00:04 jamesdlin

    Your problem is that you do:

    expect(await fetchTodo(client, 1), throwsException);
    

    expect() is a normal function, and function arguments are evaluated before the function is invoked. (Dart is an applicative-order language.) You therefore wait for fetchTodo to complete before calling expect() (and before expect can try to match against the throwsException Matcher).

    As explained by the throwsA documentation (and this applies to the throwsException Matcher too), it must be matched against a zero-argument function or a Future. You do not need to (and should not) await the call to fetchTodo.

    Also, since you're calling an asynchronous function, the expectation cannot be checked synchronously, so you'll also need to use expectLater instead of expect:

    await expectLater(fetchTodo(client, 1), throwsException);