Home > Enterprise >  how to stub a function for unit testing using mocktail
how to stub a function for unit testing using mocktail

Time:10-27

i'm using mocktail package to mock my repositories for unit testing. I already tested out few of my controllers and its passing, until i encountered a error in my submitPhoneNumber method which I cant figure out where the problem is and what am i missing.

The setup is I'm stubbing my controller.submitPhoneNumber to answer a Future.value(true), which means success. so my expectations are the authRepository.registerWithPhoneNumber method should be called 1 time and the debugState.value.hasError should be false. But i'm getting this error:

No matching calls. All calls: MockFirebaseAuthRepository.registerWithPhoneNumber( 15551234567, Closure: (String) => void, Closure: () => void)
(If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)
package:test_api                                                                                        fail
package:mocktail/src/mocktail.dart 722:7                                                                _VerifyCall._checkWith
package:mocktail/src/mocktail.dart 515:18                                                               _makeVerify.<fn>
test/src/features/authentication/presentation/sign_in/phone_sign_in/sign_in_controller_test.dart 41:13  main.<fn>.<fn>

this means that the controller.submitPhoneNumber has not been called and the debugState.value.hasError is true. But in actual, my controller is working fine in both emulator and physical device.

I cant figure out why this is happening so maybe I made mistake on how I stubbed my function. Appreciated so much if someone could help me figure this out.

Here is my test:

void main() {



  group('controller submit phone number test', () {
    final controller = SignInController(
        formType: SignInFormType.register, authRepository: authRepository);

    test('submit phonumber success', () async {
      const phoneNumber = ' 15551234567';

///this is where the error is comming from

      when(() => controller.submitPhoneNumber(phoneNumber, (e) {}, () {}))
          .thenAnswer((_) => Future.value(true));

      await controller.submitPhoneNumber(phoneNumber, (e) {}, () {});
      
      verify(() => authRepository.registerWithPhoneNumber(
          phoneNumber, (e) {}, () {})).called(1);
      expect(controller.debugState.value.hasError, false);
      expect(controller.debugState.formType, SignInFormType.register);
      expect(controller.debugState.value, isA<AsyncValue>());
    }, timeout: const Timeout(Duration(milliseconds: 500)));

}

And here is my controller

class SignInController extends StateNotifier<SignInState> {
  SignInController({
    required SignInFormType formType,
    required this.authRepository,
  }) : super(SignInState(formType: formType));
  final AuthRepository authRepository;

  Future<bool> submitPhoneNumber(String phoneNumber,
      void Function(String e) verificationFailed, VoidCallback codeSent) async {
    state = state.copyWith(value: const AsyncValue.loading());
    final value =
        await AsyncValue.guard(() => authRepository.registerWithPhoneNumber(
              phoneNumber,
              verificationFailed,
              codeSent,
            ));
    state = state.copyWith(value: value);
    return value.hasError == false;
  }

}


final signInControllerProvider = StateNotifierProvider.autoDispose
    .family<SignInController, SignInState, SignInFormType>((ref, formType) {
  final authRepository = ref.watch(authRepositoryProvider);
  return SignInController(
    authRepository: authRepository,
    formType: formType,
  );
});

CodePudding user response:

The callbacks you are passing to the controller during setup, invocation, and verification are not the same instances.

There are two ways you can fix this:

  1. Before your setup, create local variables for verificationFailed and codeSent. Pass these instead of an anonymous function.

  2. Use any<T> where T is the type of your parameter (void Function(String) and VoidCallback).

      void errorFunction(String value) {
        value;
      }

void onSentCode() {}

      when(() => controller.submitPhoneNumber(
              phoneNumber, any<void Function(String)>(), any<VoidCallback>()))
          .thenAnswer((_) => Future.value(true));
          
      await controller.submitPhoneNumber(
          phoneNumber, errorFunction, onSentCode);
          
      verify(() => controller.submitPhoneNumber(
          phoneNumber, errorFunction, onSentCode)).called(1);

  • Related