Search code examples
flutterunit-testingmockingflutter-dependenciesstacked

Mock not respecting dynamic parameters with mocktail and GetIt dependency injection


I have a mock for a SharedPreferencesService that is supposed to return a true/false depending on whether it's the user's first time using the app.

I have a parameter in my method to initialize the mock service to set the value as true/false, with a default of true, but it just seems to always be returning the default value.

I suspect this might be an issue with how my locator is set up (I'm using GetIt locator to initialize my services).

Dependency Injection

I'm using Mocktail for mocking my services.

I'm using a GetIt locator for dependency injection:

Here is my locator instance (in the main code):

GetIt locator = GetIt.instance;

void setupLocator() {
  locator.registerLazySingleton(() => AuthenticationService());
  locator.registerLazySingleton(() => FirestoreUserService());
  locator.registerLazySingleton(() => FirestoreGoalService());
  locator.registerLazySingleton(() => stacked_services.NavigationService());
  locator.registerLazySingleton(() => SharedPreferencesService());
  locator.registerLazySingleton(() => stacked_services.DialogService());
  locator.registerLazySingleton(() => LoginAndUserService());
}

And here's how SharedPreferencesService is called in my system under test (SUT):

class LoginAndUserService {
  // MARK
  // Services
  final SharedPreferencesService _prefs = locator<SharedPreferencesService>();

My test group registers services in the setUp method:

void main() {
  group('LoginAndUserService Tests -', () {
    late LoginAndUserService sut;

    setUp(() {
      registerServices();

      sut = LoginAndUserService();
    });

Register services calls this:

void registerServices() {
  getAndRegisterSharedPreferencesServiceMock();
}

And that operates as follows:

SharedPreferencesServiceMock getAndRegisterSharedPreferencesServiceMock(
    {bool? firstTimeUsage}) {
  removeRegistrationIfExists<SharedPreferencesService>();
  var service = SharedPreferencesServiceMock();

  when(() => service.firstTimeUsingApp()).thenAnswer((_) async {
    if (firstTimeUsage == null) {
      return true;
    } else {
      return firstTimeUsage;
    }
  });

  when(() => service.firstTimeAppUsageComplete()).thenAnswer((_) async => true);

  locator.registerSingleton<SharedPreferencesService>(service);
  return service;
}

So, by default, it is registering a version of this SharedPreferencesServiceMock that has the default value of true.

However, in my test, I call this specific method again, to re-register the sharedPreferencesServiceMock with a firstTimeUsage value of false.

Specific Test Info

Here is my test - it should recreate the SharedPreferencesService with a firstTimeUsage value of false.

      test(
          'loadAuthenticationPage - firstTimeUsingApp set to value in sharedprefs',
          () async {
        // ARRANGE
        var service =
            getAndRegisterSharedPreferencesServiceMock(firstTimeUsage: false);

        // ACT
        await sut.loadAuthenticationPage();
        bool firstTimeUsingAppValue = await service.firstTimeUsingApp();
        print('SharedPrefs firstTimeUsingApp is $firstTimeUsingAppValue');
        var result = sut.firstTimeUsingApp;

        // ASSERT
        expect(result, false);
      });

However, both the print statement and the result are coming back as true so somehow the mock service must not be returning that value appropriately. Any ideas what I might be missing?


Solution

  • I've found two solutions:

    First, make sure I initialize the SUT after I update the locator.

    test(
        'loadAuthenticationPage - firstTimeUsingApp set to value in sharedprefs',
        () async {
      // ARRANGE
      var service =
          getAndRegisterSharedPreferencesServiceMock(firstTimeUsage: false);
      // sut is specified in the group - I need to re-initialize this after the locator.
      sut = LoginAndUserService();
    
      // ACT
      await sut.loadAuthenticationPage();
      bool firstTimeUsingAppValue = await service.firstTimeUsingApp();
      print('SharedPrefs firstTimeUsingApp is $firstTimeUsingAppValue');
      var result = sut.firstTimeUsingApp;
    
      // ASSERT
      expect(result, false);
    });
    

    The other is just overriding the when response directly in my test, instead of in my loading of the locator.

    I'm still interested in whether there's a way to do this in the loads of my locator, but this seems to work properly.

    
          test(
              'loadAuthenticationPage - firstTimeUsingApp set to value in sharedprefs',
              () async {
            // ARRANGE
            var service = locator<SharedPreferencesService>();
    // update my when response manually here.
            when(() => service.firstTimeUsingApp()).thenAnswer((_) async => false);
    
            // ACT
            await sut.loadAuthenticationPage();
            bool firstTimeUsingAppValue = await service.firstTimeUsingApp();
            print('SharedPrefs firstTimeUsingApp is $firstTimeUsingAppValue');
            var result = sut.firstTimeUsingApp;
    
            // ASSERT
            expect(result, false);
          });