Mocking a DateTime parameter input - flutter

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.

Related

unable to register an abstract class in using injectable and get_it packages

I'm using injectable and get_it in flutter dart (following the very popular Reso coder)
I have a simple abstract class:
import 'package:injectable/injectable.dart';
//#injectable
abstract class IRandomQuantityRepository {
Future<int> getRandomQuantity();
}
and I have two simple concrete implementations of it:
import 'package:injectable/injectable.dart';
#dev
#injectable
class DevRandomQuantityRepository implements IRandomQuantityRepository {
const DevRandomQuantityRepository();
#override
Future<int> getRandomQuantity() async => 90;
}
and
import 'dart:math';
import 'package:injectable/injectable.dart';
#prod
#injectable
class RandomQuantityRepository implements IRandomQuantityRepository {
const RandomQuantityRepository();
#override
Future<int> getRandomQuantity() async => Random().nextInt(100);
}
Lastly, I have an injection.dart:
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:moontreeapp/injection.config.dart';
final GetIt getIt = GetIt.instance;
#InjectableInit(preferRelativeImports: false)
void configureInjection(String env) {
$initGetIt(getIt, environment: env);
}
abstract class Env {
static const prod = 'prod';
static const dev = 'dev';
}
besides all that I have a bloc that wants to use stuff:
#injectable
class RandomQuantityBloc
extends Bloc<RandomQuantityEvent, RandomQuantityState> {
final IRandomQuantityRepository _quantityFacade; // notice this final...
doesn't that look good? I think so. So then I run this command to make the generated code flutter pub run build_runner watch
But I get a message:
[RandomQuantityBloc] depends on unregistered type [IRandomQuantityRepository]... Did you forget to annotate the above class(s) or their implementation with #injectable?
or add the right environment keys?
Ok, so that's cool, lets add it to the interface:
import 'package:injectable/injectable.dart';
#injectable // <-- added
abstract class IRandomQuantityRepository {
Future<int> getRandomQuantity();
}
but then I get a new error:
> [IRandomQuantityRepository] is abstract and can not be registered directly!
> if it has a factory or a create method annotate it with #factoryMethod
> 14 │ abstract class IRandomQuantityRepository {
> │ ^^^^^^^^^^^^^^^^^^^^^^^^^
In the past I've handled dependency injection manually, so I'm new to these packages, what am I missing here?
Besides all that, the real issue is that I can't switch the injection based on the environment. I can use get_it to get a concrete dependency but not one based on environment like in this test:
/// has no effect:
configureInjection(Env.dev);
/// gets prod version:
final devRandomQuantity = getIt<RandomQuantityRepository>();
So something about this whole set up isn't configuring the Injections correctly... What am I missing?
One final thing that might be useful is to see the generated code:
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// InjectableConfigGenerator
// **************************************************************************
import 'package:get_it/get_it.dart' as _i1;
import 'package:injectable/injectable.dart' as _i2;
import 'package:moontreeapp/application/quantity/bloc/randomquantity_bloc.dart'
as _i5;
import 'package:moontreeapp/domain/quantity/i_randomquantity_repository.dart' as _i6;
import 'package:moontreeapp/infrastructure/quantity/dev_randomquantity_repository.dart'
as _i3;
import 'package:moontreeapp/infrastructure/quantity/mock_randomquantity_repository.dart'
as _i4;
import 'package:moontreeapp/infrastructure/quantity/randomquantity_repository.dart'
as _i7;
const String _dev = 'dev';
const String _prod = 'prod';
// ignore_for_file: unnecessary_lambdas
// ignore_for_file: lines_longer_than_80_chars
/// initializes the registration of provided dependencies inside of [GetIt]
_i1.GetIt $initGetIt(_i1.GetIt get,
{String? environment, _i2.EnvironmentFilter? environmentFilter}) {
final gh = _i2.GetItHelper(get, environment, environmentFilter);
gh.factory<_i3.DevRandomQuantityRepository>(
() => _i3.DevRandomQuantityRepository(),
registerFor: {_dev});
gh.factory<_i5.RandomQuantityBloc>(
() => _i5.RandomQuantityBloc(get<_i6.IRandomQuantityRepository>()));
gh.factory<_i7.RandomQuantityRepository>(() => _i7.RandomQuantityRepository(),
registerFor: {_prod});
return get;
}
Do I put #injectable on abstract classes or not?
ok, so I guess injectable can't see what the class implements so you have to make it explicit. also, I missed that #dev isn't built in, you have to make it.
So this is the proper way to use the decorations
#Environment('dev')
#Injectable(as: IRandomQuantityRepository)
class DevRandomQuantityRepository implements IRandomQuantityRepository {
const DevRandomQuantityRepository();
#override
Future<Either<QuantFailure, Quantity>> getRandomQuantity() async =>
right(Quantity(Amount(900.0001), Decimal(4)));
}
and that way this does work:
configureInjection(Env.dev);
final mockRandomQuantity = getIt<IRandomQuantityRepository>();
await mockRandomQuantity.getRandomQuantity();
// 90

Flutter mocktail get error when put mock getx controller

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);
});
}

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

How to test nulls using mockito?

I am using mockito 4.1.3 , and here I have some test class:
import 'package:flutter_test/flutter_test.dart';
import 'package:ghinbli_app/models/film_model.dart';
import 'package:ghinbli_app/network/ghibli_films.dart';
import 'package:mockito/mockito.dart';
class MockClient extends Mock implements GhibliFilms {
#override
Future<List<FilmModel>> getFilms() async{
return null;
}
}
void main() {
final GhibliFilms ghibliMock = MockClient();
test('If API call was unsuccessful and data received is null', () {
expect(ghibliMock.getFilms(), null);
});
}
Inside the MockClient class, I am overriding a method called getFilms() and returning null to simulate a situation when a call to some API returns null as data.
A problem
When I try to check if getFilms() actually returns a null value my test will fail with this error (probably because of the return type of getFilms()):
Expected: <null>
Actual: <Instance of 'Future<List<FilmModel>>'>
How can I check and test that the data from getFilms() is actually null, what am I doing wrong?
I've tested your code and got same error as you. After making these changes everything runs fine, try it yourself.
class MockClient extends Mock implements GhibliFilms {
#override
Future<List<FilmModel>> getFilms() async {
return Future.value(null); // this is not that important
}
}
void main() {
final GhibliFilms ghibliMock = MockClient();
// async/await here was important
test('If API call was unsuccessful and data received is null', () async {
expect(await ghibliMock.getFilms(), null);
});
}

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.