mocktail icon indicating copy to clipboard operation
mocktail copied to clipboard

Stub / When a closure / lambda expression

Open petrnymsa opened this issue 1 year ago • 1 comments

Suppose we have following class (DbContext from Drift Sqlite database)


class LocalDatabaseContext with AppLoggy {
  final LocalDatabase _db;

  LocalDatabaseContext({LocalDatabase? db}) : _db = db ?? DI.resolve<LocalDatabase>();
 
  Future<Either<T, LocalDatabaseError>> profileSessions<T>(Future<T> Function(ProfileSessionsDao) callback) async {
    return _wrapRequest(callback, _db.profileSessionsDao);
  }
 
 // this method we want stub / verify
  Future<Either<T, LocalDatabaseError>> userProfiles<T>(Future<T> Function(UserProfilesDao) callback) async {
    return _wrapRequest(callback, _db.userProfilesDao);
  }

  Future<Either<T, LocalDatabaseError>> _wrapRequest<T, DAO>(Future<T> Function(DAO) callback, DAO dao) async {
    try {
      final T result = await callback.call(dao);
      return Left(result);
    } catch (e) {
      loggy.error('Local database call failed with error: ${e.toString()}', e, StackTrace.current);
      return Right(UnkownError(Exception(e.toString())));
    }
  }
}

and a class which uses this Context


Future<Either<UserProfile?, LocalDatabaseError>> getByUserNameAndServer(String userName, ApiServer server) =>
      _dbContext.userProfiles((dao) => dao.getByUserNameAndServer(userName: userName, server: server));

This class I want to unit test, it contains more complex methods, this is just an example.

// stub
when(() => dbContext.userProfiles<UserProfile?>(
              (dao) => dao.getByUserNameAndServer(userName: any(named: 'userName'), server: any(named: 'server'))))
          .thenAnswer((_) async {
        return Left(profile);
      });

// act

// this method calls internally getByUserNameAndServer
 final result = await store.createIfAbsent(userName: 'user1', selectedServer: server);    

// verify
// ....

Issue is that as soon as code hits _dbContext.userProfiles((dao) => dao.getByUserNameAndServer(userName: userName, server: server)) it throws type 'Null' is not a subtype of type 'Future<Either<UserProfile?, LocalDatabaseError>>

This means, that mocktail did not matched "closure" call.

One of the workaround is to use any()

   when(() => dbContext.userProfiles<UserProfile?>(any())).thenAnswer((_) async {
        return Left(profile);
      });

but if method under the test uses dbContext multiple times with different "closures" it is not possible to stub it properly and test outcome behavior.

Any ideas how to solve it?

petrnymsa avatar Jul 18 '22 08:07 petrnymsa

I got the same error! any help, please!

chornthorn avatar Jul 29 '22 15:07 chornthorn

Hi @petrnymsa 👋 Using the any matcher should work in this case. If this is still an issue please share a link to a complete minimal reproduction sample and I'm happy to take a closer look, thanks!

felangel avatar Apr 20 '24 03:04 felangel