3

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

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for showing how to catch and test Completer.completeError. I could see my code returning the right thing, but I couldn't work out how to test for it.
How does it works for you without returning a value from the test catchError. My test fails unless I return the empty future.
Can you make try catch version of this test? My try catch can not catch the future.error()
Instead of .thenAnswer((_) => Future.error('error')) it's better to write .thenThrow('error')
@AlexSemeniuk Pay attention! .thenThrow makes the invocation itself throw synchronically, .thenAnswer((_) => Future.error('error')) makes the Future returned by the invocation throw!
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.