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:
Before your setup, create local variables for
verificationFailed
andcodeSent
. Pass these instead of an anonymous function.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);