Prajval Prabhakar
Prajval Prabhakar

Reputation: 313

What is the proper way to unittest future errors in flutter/dart?

The unittest that I am writing should fail on exception and I would like to match the error thrown using a matcher, but I am getting an error.

What am I doing wrong currently and what is the error here? If I am doing it incorrectly, what is the correct way to test the error scenario for a method that returns a future? And why is there an asynchronous gap?

Here's the method I want to test :

Future<void> registerUser(String name, String email, String password) async {
auth.signUp(email, password, name).then((_) {
      auth.getCurrentUser().then((user) async {
        // Doing something
    }).catchError((onError) {
      throw onError;
    });

Here's the test I have written :

test('should fail to register if fetching user fails', () async {
      MockAuth auth = MockAuth();
      RegisterRepository repo = RegisterRepository(auth);

      String password = 'password';
      String email = 'email';
      String name = 'name';

      when(auth.signUp(email, password, name))
          .thenAnswer((_) => Future.value());
      when(auth.getCurrentUser()).thenThrow((_) => throw Error());

      try {
        await repo.registerUser(name, email, password);
        fail('exception not thrown');
      } catch (e) {}

      verify(auth.signUp(email, password, name)).called(1);
      verify(auth.getCurrentUser()).called(1);
      verifyNoMoreInteractions(auth);
    });

I am getting this error :

package:mockito/src/mock.dart 403:7               PostExpectation.thenThrow.<fn>
package:mockito/src/mock.dart 130:45              Mock.noSuchMethod
package:dive/repository/register_repo.dart 25:12  RegisterRepository.registerUser.<fn>
===== asynchronous gap ===========================
dart:async                                        Future.then
package:dive/repository/register_repo.dart 24:40  RegisterRepository.registerUser
test/repository/register_repo_test.dart 61:20     main.<fn>.<fn>

Closure: (dynamic) => Null

Upvotes: 3

Views: 4498

Answers (2)

user3193413
user3193413

Reputation: 634

Just found that we can simply do this (check out the throwsA):

test("getNews SHOULD throw an exception WHEN api fails", () {
  final ExceptionMock exception = ExceptionMock();
  when(() => newsApi.getNews()).thenAnswer((realInvocation) => Future<List<JsonNews>>.error(exception));

  expect(() => sut.getNews(), throwsA(exception));
});

Please notice, I'm mocking the exception. This way I'm confident that the api is actually being called. Otherwise I wouldn't catch that mocked exception. When using concrete object, one can't be sure if a value is being returned because the dependency actually returns that object or it is just hardcoded in the sun (system under test) implementation.

Upvotes: 1

Prajval Prabhakar
Prajval Prabhakar

Reputation: 313

For anyone else who faces this problem in the future,

  1. I removed the async keyword from the method that is being tested, async keyword is not necessary here. Chaining the calls makes it more readable :
Future<void> registerUser(String name, String email, String password) {
return auth
        .signUp(email, password, name)
        .then((_) => auth.getCurrentUser())
        .then((user) {
        // Doing something
    }).catchError((onError) {
      throw onError;
    });
  1. To test the error from the future, do the following :
test('should fail to register if fetching user fails', () async {
      MockAuth auth = MockAuth();
      RegisterRepository repo = RegisterRepository(auth);

      String password = 'password';
      String email = 'email';
      String name = 'name';

      when(auth.signUp(email, password, name))
          .thenAnswer((_) => Future.value());
      when(auth.getCurrentUser()).thenAnswer((_) => Future.error('error'));

      repo.registerUser(name, email, password).catchError((onError) {
        expect(onError.toString(), 'error');

        verify(auth.signUp(email, password, name)).called(1);
        verify(auth.getCurrentUser()).called(1);
        verifyNoMoreInteractions(auth);
      });
    });

The tests pass now.

Upvotes: 4

Related Questions