Flutter mocktail get error when put mock getx controller - flutter

I have this getx controller :
class CustomerPOAutocompleteState extends GetxController {
CustomerPOAutocompleteState();
...
Future<void> getCustomer(String title, String? propertyId) async {
var result = await RRGraphQL().artemis!...
_customers =...
update(['update']);
}
Now by Mock mocktail I created a mock class:
class MockController extends Mock implements CustomerPOAutocompleteState {}
When I try to put it I got error:
void main() {
late MockController mockController;
setUpAll(() {
mockController = MockController();
});
...
testWidgets('test', (WidgetTester tester) async {
Get.put<CustomerPOAutocompleteState>(mockController,tag: "");
error:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following _TypeError was thrown running a test:
type 'Null' is not a subtype of type 'InternalFinalCallback<void>'

UPDATED Answer
This error occurs since Get.put performs calls on your GetxController which have not been mocked out correctly.
You can either do this manually (you will have to check the implementation details of Get.put and GetxController). Or you can do, what the Get documentation suggests and let your mock extend GetxController and use Mock as a mixin.
class MockController extends GetxController
with Mock
implements CustomerPOAutocompleteState {}
Check out this full example:
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:mocktail/mocktail.dart';
class DemoController extends GetxController {
var count = 0.obs;
increment() => count++;
}
class MockController extends GetxController
with Mock
implements DemoController {}
main() {
late MockController mockController;
setUpAll(() {
mockController = MockController();
});
test('GetX Test', () {
when(() => mockController.count).thenAnswer((_) => 12.obs);
Get.put<DemoController>(mockController, tag: "");
mockController.increment();
expect(mockController.count.value, 12);
});
}

Related

type 'Null' is not a subtype of type 'Future<Either<Failure, NumberTrivia>>'

I was learning how to do test drive development (tdd) as well as clean code architecture on flutter and I kept getting into unfamiliar problem again and again. That is - type 'Null' is not a subtype of type 'Future<Either<Failure, NumberTrivia>>'. This method is declared in an abstract class NumberTriviaRepository
The NumberTrivia is a simple entity class as follow
class NumberTrivia extends Equatable {
final int number;
final String text;
const NumberTrivia({
required this.number,
required this.text,
});
#override
// TODO: implement props
List<Object?> get props => [number, text];
}
The NumberTriviaRepository
abstract class NumberTriviaRepository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
The usecase - GetConcreteNumberTrivia
class GetConcreteNumberTrivia {
final NumberTriviaRepository repository;
GetConcreteNumberTrivia(this.repository);
Future<Either<Failure, NumberTrivia>> execute({required number}) async {
return await repository.getConcreteNumberTrivia(number);
}
}
Here is the main test file
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
late MockNumberTriviaRepository mockNumberTriviaRepository;
late GetConcreteNumberTrivia usecase;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
final testNumber = 1;
const testNumberTrivia = NumberTrivia(number: 1, text: 'test');
test('should get trivia for the number from the repository', () async {
when(mockNumberTriviaRepository.getConcreteNumberTrivia(testNumber))
.thenAnswer((_) async => const Right(testNumberTrivia));
final result = await usecase.execute(number: testNumber);
log('Result equals ${result}');
expect(result, equals(const Right(testNumberTrivia)));
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(testNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
});
}
I don't know where I'm making a mistake but I'm having trouble passing this test case. I believe both the results should be NumberTrivia object but it seems like one of them is null and I can't figure why that is the case.
I expect the object to be of the same type (NumberTrivia) in the expect function during the test
This might be caused if you are using an old Mockito version that does not support null-safety. Try upgrading your version to ^5.0.0.
This is frequently a pain if tutorials are from pre-null-safety or if some packages have significantly changed since then. Not sure if it was this or some bloc tutorial, one was causing me real headaches because it required significant changes to run in today's version.
If you use version to ^5.0.0.
To use Mockito's generated mock classes, add a build_runner dependency in your package's pubspec.yaml file, under dev_dependencies; something like build_runner: ^1.11.0.
flutter pub run build_runner build
# OR
dart run build_runner build
The resulting class is as follows, and you will pass the test
get_concrete_number_trivia_test.mocks.dart
// Mocks generated by Mockito 5.3.2 from annotations
// in flutter_go/test/features/number_trivia/domain/usecases/get_concrete_number_trivia_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i4;
import 'package:dartz/dartz.dart' as _i2;
import 'package:flutter_go/core/error/failures.dart' as _i5;
import 'package:flutter_go/features/number_trivia/domain/entities/number_trivia.dart'
as _i6;
import 'package:flutter_go/features/number_trivia/domain/repositories/number_trivia_repository.dart'
as _i3;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeEither_0<L, R> extends _i1.SmartFake implements _i2.Either<L, R> {
_FakeEither_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [NumberTriviaRepository].
///
/// See the documentation for Mockito's code generation for more information.
class MockNumberTriviaRepository extends _i1.Mock
implements _i3.NumberTriviaRepository {
#override
_i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>> getConcreteNumberTrivia(
int? number) =>
(super.noSuchMethod(
Invocation.method(
#getConcreteNumberTrivia,
[number],
),
returnValue:
_i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
_FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
this,
Invocation.method(
#getConcreteNumberTrivia,
[number],
),
)),
returnValueForMissingStub:
_i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
_FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
this,
Invocation.method(
#getConcreteNumberTrivia,
[number],
),
)),
) as _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>);
#override
_i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>
getRandomNumberTrivia() => (super.noSuchMethod(
Invocation.method(
#getRandomNumberTrivia,
[],
),
returnValue:
_i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
_FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
this,
Invocation.method(
#getRandomNumberTrivia,
[],
),
)),
returnValueForMissingStub:
_i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
_FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
this,
Invocation.method(
#getRandomNumberTrivia,
[],
),
)),
) as _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>);
}
Example
import 'package:dartz/dartz.dart';
import 'package:flutter_go/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:flutter_go/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:flutter_go/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
// class MockNumberTriviaRepository extends Mock
// implements NumberTriviaRepository {}
#GenerateNiceMocks([MockSpec<NumberTriviaRepository>()])
// import generated mock classes
import './get_concrete_number_trivia_test.mocks.dart';
void main() {
late GetConcreteNumberTrivia usecase;
late MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
final tNumber = 1;
final tNumberTrivia = NumberTrivia(number: 1, text: 'test');
test(
'should get trivia for the number from the repository',
() async {
// "On the fly" implementation of the Repository using the Mockito package.
// When getConcreteNumberTrivia is called with any argument, always answer with
// the Right "side" of Either containing a test NumberTrivia object.
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
// The "act" phase of the test. Call the not-yet-existent method.
final result = await usecase.execute(number: tNumber);
// UseCase should simply return whatever was returned from the Repository
expect(result, Right(tNumberTrivia));
// Verify that the method has been called on the Repository
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
// Only the above method should be called and nothing more.
verifyNoMoreInteractions(mockNumberTriviaRepository);
},
);
}

The argument type 'Future<UserCredential>' can't be assigned to the parameter type 'dynamic Function()'

I am learning about TDD and practicing my test writing. I want to write a test for my login through Firebase.
here is the test file:
import 'package:firebase/firebase.dart';
import 'package:firebase_auth/firebase_auth.dart' as fa;
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scopetik/features/login/data/login_data_impl.dart';
import 'package:scopetik/features/login/models/user_model.dart';
class MockFirebaseAuth extends Mock implements fa.FirebaseAuth {}
class MockFirebaseUser extends Mock implements fa.User {}
class MockAuthResult extends Mock implements fa.UserCredential {}
void main() {
late LoginDataImpl sut;
late MockFirebaseAuth mockFirebaseAuth;
setUp(() {
mockFirebaseAuth = MockFirebaseAuth();
sut = LoginDataImpl(mockFirebaseAuth);
});
group(
'Test LoginDataImpl class',
() {
when(mockFirebaseAuth.signInWithEmailAndPassword(
email: 'email', password: 'password'))
.thenAnswer((_) async {
return MockAuthResult();
});
test(
"get UserModel",
() async {
//arrange
},
);
},
);
}
I don't know why the when function won't let me return a future<UserCredential>.
I also had the same problem. You need to use mockito instead of mocktail in order to do that.
You can find when property of both libraries here, mocktail, mockito

Flutter - How to retrieve an implemented class with Get.find after registered with Get.put using GetX?

I have a class called GetConnectApiHelper that implemented an abstraction called IApiHelper, I need to register this class with Get.put inside Bindings and retrieve the implementation inside an abstraction variable but when I try to do that I get an error about "the abstraction is not registered".
How can I inject the dependency correctly making it easy to change in case I need to replace with http, dio etc...(clean architecture)
abstract class IApiHelper {}
class GetConnectApiHelper extends GetxService implements IApiHelper {}
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put(GetConnectApiHelper());
Get.put(SignInController());
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper = Get.find(); // This throws the exception
}
======== Exception caught by widgets library =======================================================
The following message was thrown building Builder(dirty):
"IApiHelper" not found. You need to call "Get.put(IApiHelper())" or "Get.lazyPut(()=>IApiHelper())"
I found a solution. I can set the Interface as a Type and then register the implementation I want to be retrieved.
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put<IApiHelper>(GetConnectApiHelper());
Get.put(SignInController());
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper = Get.find();
}
print(apiHelper.runtimeType); // it prints Instance of 'GetConnectApiHelper'
Or I can inject the implementation.
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put<IApiHelper>(GetConnectApiHelper());
Get.put(SignInController(apiHelper: Get.find()));
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper;
SignInController({required this.apiHelper})
}
GetX finds its dependencies based on its exact types so you need to use Get.find<GetConnectApiHelper>()
updated:
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put(GetConnectApiHelper());
Get.put(SignInController<GetConnectApiHelper>());
}
}
class SignInController<T extends IApiHelper> extends GetxController {
final IApiHelper apiHelper = Get.find<T>();
}

Mocking a DateTime parameter input

Has anyone had issues supplying the any Mockito method to a DateTime mock method parameter? Basically I have this method implemented on the mock class Future<int> testMethod(DateTime datetime).
So when I tried to stub the response on the test scenario through when(mockClass.testMethod(any)).thenAnswer((_) async => 1), this shows always on IntelliJ line editor - error: The argument type 'Null' can't be assigned to the parameter type 'DateTime'. (argument_type_not_assignable).
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
abstract class TestClass {
Future<int> testMethod(DateTime dateTime);
}
class MockTestClass extends Mock implements TestClass {}
class InvokeClass {
final TestClass testClass;
InvokeClass(this.testClass);
}
// #GenerateMocks([InvokeClass, TestClass])
void main() {
TestClass testClass = MockTestClass();
InvokeClass invokeClass;
setUp(() {
testClass = MockTestClass();
invokeClass = InvokeClass(testClass);
});
test('', () async {
when(testClass.testMethod(any)).thenAnswer((_) => 1);
});
}
Have you tried making the parameter Nullable so that the method becomes
testMethod(DateTime? datetime)
The easiest way is to use the code generator from mockito 5.0.0: https://pub.dev/packages/mockito#lets-create-mocks
basically you would use:
abstract class MyClass {
Future<int> testMethod(DateTime dateTime);
}
#GenerateMocks([MyClass])
void main() {
final mock = MockMyClass();
when(mock.testMethod(any)).....
}
the generator will create a method which takes a nullable DateTime, so any will work. see the mockito documentation for more details.
Update: giving your example it should look like:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'testclass.mocks.dart'; // this will be generated by `build_runner build`
abstract class TestClass {
Future<int> testMethod(DateTime dateTime);
}
class InvokeClass {
final TestClass testClass;
InvokeClass(this.testClass);
}
#GenerateMocks([TestClass])
void main() {
MockTestClass testClass = MockTestClass();
InvokeClass invokeClass;
setUp(() {
testClass = MockTestClass();
invokeClass = InvokeClass(testClass);
});
test('', () async {
when(testClass.testMethod(any)).thenAnswer((_) => 1);
});
}
after those changes run flutter run build_runner build or dart run build_runner build.

what is the correct approach to test riverpod with mockito

what is the correct approach to test riverpod with mockito?
running the code above,
/// ### edited snippets from production side ###
/// not important, skip to the TEST below!
/// this seems meaningless just because it is out of context
mixin FutureDelegate<T> {
Future<T> call();
}
/// delegate implementation
import '../../shared/delegate/future_delegate.dart';
const k_STRING_DELEGATE = StringDelegate();
class StringDelegate implements FutureDelegate<String> {
const StringDelegate();
#override
Future<String> call() async {
/// ... returns a string at some point, not important now
}
}
/// the future provider
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '<somewhere>/delegate.dart'; /// the code above
final stringProvider = FutureProvider<String>((ref) => k_STRING_DELEGATE());
/// ### edited snippets from TEST side ###
/// mocking the delegate
import 'package:mockito/mockito.dart';
import '<see above>/future_delegate.dart';
class MockDelegate extends Mock implements FutureDelegate<String> {}
/// actual test
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/all.dart';
import 'package:mockito/mockito.dart';
import '<somewhere in my project>/provider.dart';
import '../../domain/<somewhere>/mock_delegate.dart'; // <= the code above
void main() {
group('`stringProvider`', () {
final _delegate = MockDelegate();
test('WHEN `delegate` throws THEN `provider`return exception',
() async {
when(_delegate.call()).thenAnswer((_) async {
await Future.delayed(const Duration(seconds: 1));
throw 'ops';
});
final container = ProviderContainer(
overrides: [
stringProvider
.overrideWithProvider(FutureProvider((ref) => _delegate()))
],
);
expect(
container.read(stringProvider),
const AsyncValue<String>.loading(),
);
await Future<void>.value();
expect(container.read(stringProvider).data.value, [isA<Exception>()]);
});
});
}
running the test returns
NoSuchMethodError: The getter 'value' was called on null.
Receiver: null
Tried calling: value
dart:core Object.noSuchMethod
src/logic/path/provider_test.dart 28:48 main.<fn>.<fn>
I'm new to riverpod, clearly I'm missing something
I tried to follow this
I found that I had some extra errors specifically when using StateNotifierProvider. The trick was to not only override the StateNotifierProvider, but also its state property (which is a StateNotifierStateProvider object).
class SomeState {
final bool didTheThing;
SomeState({this.didTheThing = false});
}
class SomeStateNotifier extends StateNotifier<SomeState> {
SomeStateNotifier() : super(SomeState());
bool doSomething() {
state = SomeState(didTheThing: true);
return true;
}
}
final someStateProvider = StateNotifierProvider<SomeStateNotifier>((ref) {
return SomeStateNotifier();
});
class MockStateNotifier extends Mock implements SomeStateNotifier {}
void main() {
final mockStateNotifier = MockStateNotifier();
when(mockStateNotifier.doSomething()).thenReturn(true);
final dummyState = SomeState(didTheThing: true); // This could also be mocked
ProviderScope(
overrides: [
someStateProvider.overrideWithValue(mockStateProvider), // This covers usages like "useProvider(someStateProvider)"
someStateProvider.state.overrideWithValue(dummyState), // This covers usages like "useProvider(someStateProvider.state)"
],
child: MaterialApp(...),
);
}
There are 2 errors in your code
You're trying to test a throw error, so you should use thenThrow instead of thenAnswer, but because you're overriding a mixing method I would recommend instead of using Mock use Fake (from the same mockito library) to override methods and then throw it as you want
class MockDelegate extends Fake implements FutureDelegate<String> {
#override
Future<String> call() async {
throw NullThrownError; //now you can throw whatever you want
}
}
And the second problem (and the one your code is warning you) is that you deliberately are throwing, so you should expect an AsyncError instead, so calling container.read(stringProvider).data.value is an error because reading the riverpod documentation:
When calling data:
The current data, or null if in loading/error.
so if you're expecting an error (AsyncError) data is null, and because of that calling data.value its the same as writing null.value which is the error you're experiencing
This is the code you could try:
class MockDelegate extends Fake implements FutureDelegate<String> {
#override
Future<String> call() async {
throw NullThrownError;
}
}
void main() {
group('`stringProvider`', () {
final _delegate = MockDelegate();
test('WHEN `delegate` throws THEN `provider`return exception', () async {
final container = ProviderContainer(
overrides: [
stringProvider
.overrideWithProvider(FutureProvider((ref) => _delegate.call()))
],
);
expect(container.read(stringProvider), const AsyncValue<String>.loading());
container.read(stringProvider).data.value;
await Future<void>.value();
expect(container.read(stringProvider), isA<AsyncError>()); // you're expecting to be of type AsyncError because you're throwing
});
});
}
Also consider mocking out various providers by using an Override in your top level ProviderScope. That's what override can do quite well.