简体   繁体   中英

How to test Function passed as an argument in Flutter?

How to test Function passed as an argument in Flutter?

code:

Future<User> execute({
    required String username,
    required String password,
    required void Function(AuthFailure fail) onFailure,
    required void Function(User user) onSuccess,
}) async {
    if (username.isNonValid || password.isNonValid) {
      onFailure(const AuthFailure.wrongCredentials()); // I want to test this line
      return const User.anonymous();
    }
    ...
}

test:

  test('use case - failure execution for incorrect credentials', () async {
    // GIVEN
    // WHEN
    final user = await useCase.execute(
      username: "noname",
      password: "password",
      onFailure: (fail) {},
      onSuccess: (user) {},
    );
    // THEN
    // TODO how to verify onFailure call inside useCase?
    expect(user, const User.anonymous());
  });

Or maybe testing this way is not the idiomatic way, because the test becomes more white-box instead black-box? Should I perceive passing functions as arguments to use cases as anti-pattern? I can change it then. The proposition is to return sth like Either from useCase.execute() :

Future<Either<Failure, Success>> execute({
    required String username,
    required String password,
}) async {
    if (username.isEmpty || password.isEmpty) {
      // return wrapper around AuthFailure.wrongCredentials()) of Either left subtype (Either has two subtypes)
    }
    ...
}

This way I only verify return type, and all the lines are covered this way. It's gonna work, but I feel better with the simplest, not the smartest solution.

PS I use Mocktail for mocking, but using Mockito in solution is also warmly welcomed.

If you just want to verify that the callback is triggered, I personally would just make your callback set a flag and then test that flag afterward, which I think is straightforward, simple, and easy to understand with no magic:

test('use case - failure execution for incorrect credentials', () async {
  var failureCalled = false;

  final user = await useCase.execute(
    username: "noname",
    password: "password",
    onFailure: (fail) => failureCalled = true,
    onSuccess: (user) {},
  );

  expect(user, const User.anonymous());
  expect(failureCalled, true);
});

But if you really want to use Mock s, you will need some Mock object to use and to call instance methods on that in callbacks. With Mockito you could do:

test('use case - failure execution for incorrect credentials', () async {
  dynamic mock = Mock();

  final user = await useCase.execute(
    username: "noname",
    password: "password",
    onFailure: (fail) => mock.fail(fail),
    onSuccess: (user) {},
  );

  expect(user, const User.anonymous());
  verify(mock.fail(any)).called(1);
});

Some things to note:

  • To avoid declaring a class with the expected instance methods and then code-generating stubs, create a raw Mock instance but declare it as dynamic to disable static type-checking. This will then take advantage the Mock.noSuchMethod implementation.
  • You can't use onFailure: mock.fail directly since the Mock has no generated stubs, and mock.fail will just be null instead of a Function .

I am not experienced with Mocktail, but I imagine that you could do something similar.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM