Flutter mockito unit test error after migrate null-safety - flutter

Null* get any
package:mockito/src/mock.dart
An argument matcher that matches any argument passed in "this" position.
The argument type 'Null*' can't be assigned to the parameter type 'Params'.dart(argument_type_not_assignable)
test('should start listening when StartListening is called', () async {
final tStreamListHazardEntity =
Stream<List<HazardEntity>>.fromIterable([tListHazardEntity]);
// arrange
when(mockHazardListen!.call(any))
.thenAnswer((_) async => Right(tStreamListHazardEntity));
// assert
expectLater(
bloc,
emitsInOrder([
isA<HazardMasterInitial>(),
isA<Loading>(),
isA<UpdateState>()
]));
// act
bloc!.add(StartListening());
await untilCalled(mockHazardListen!.call(any));
// assert
verify(mockHazardListen!.call(any)); });

I found the solution, Used mockito can creating mocks without creating manual mocks when after flutter null-safety migration .
Mock library for Dart inspired by mockito.
Mocktail focuses on providing a familiar, simple API for creating mocks in Dart (with null-safety) without the need for manual mocks or code generation.

Related

Flutter unit test - How to test that an access to a field throws a LateInitializationError

I want to test a class where a late field is not yet initialized.
Here is the code of my test:
test('Set lang method', () {
// Throws an exception because the field osLang is not supposed to be initialized yet.
expect(() => dbRepository.osLang, throwsException);
dbRepository.setOsLang('fr');
expect(dbRepository.osLang, 'fr');
});
Unfortunately, I get this error when I run my test:
package:test_api expect
package:flutter_test/src/widget_tester.dart 455:16 expect
test/src/models/repositories/db_repository_test.dart 20:7 main.<fn>.<fn>
Expected: throws <Instance of 'Exception'>
Actual: <Closure: () => String>
Which: threw LateError:<LateInitializationError: Field '_osLang#19447861' has not been initialized.>
stack package:my_app/src/models/repositories/db_repository.dart DBRepository._osLang
package:my_app/src/models/repositories/db_repository.dart 18:24 DBRepository.osLang
test/src/models/repositories/db_repository_test.dart 20:33 main.<fn>.<fn>.<fn>
package:test_api expect
package:flutter_test/src/widget_tester.dart 455:16 expect
test/src/models/repositories/db_repository_test.dart 20:7 main.<fn>.<fn>
which is not an instance of 'Exception'
I tried to change the throwsException with throwsA(isA<LateInitializationError>) but my IDE doesn't find any class called LateInitializationError.
I can't find a solution to that problem on the flutter documentation.
Errors aren't Exceptions, so throwsException won't match it.
LateInitializationError used to be a public type, but it seems that it isn't public anymore. You could resort to throwsA(isA<Error>()). (Based on the last available public documentation for it, it derives directly from Error, so there isn't a more specific type to test against.) You could check the Error string to be more specific:
expect(
() => dbRepository.osLang,
throwsA(
predicate<Error>(
(error) => error.toString().contains('LateInitializationError'),
),
),
);
All that said, in my opinion, testing for LateInitializationError might be a bit questionable anyway. Ideally late variables shouldn't be exposed as part of your API. If your code requires an explicit initialization step, then it'd be clearer to have an explicit check with a descriptive error message. That is, instead of:
late final String someProperty;
you could have something like:
String? _someProperty;
String get someProperty {
var _someProperty = this._someProperty;
if (_someProperty == null) {
throw StateError('init() must be called first');
}
return _someProperty;
}

Missing stub error on mockito in flutter. Trying to use setString on mocked SharedPreferences

I'm trying to mock sharedPreferences using Mockito in my flutter project. Here is the error log.
package:mockito/src/mock.dart 190:7 Mock._noSuchMethod
package:mockito/src/mock.dart 184:45 Mock.noSuchMethod
test\feature\number_trivia\data\datasource\number_trivia_local_datasource_test.mocks.dart 67:14 MockSharedPreferences.setString
package:clean_arch_tdd/features/number_trivia/data/datasources/number_trivia_local_datasource.dart 31:30 NumberTriviaLocalDataSourceImpl.cacheLastNumberTrivia
test\feature\number_trivia\data\datasource\number_trivia_local_datasource_test.dart 51:18 main.<fn>.<fn>
MissingStubError: 'setString'
No stub was found which matches the arguments of this method call:
setString('CACHED_NUMBER_TRIVIA', '{"text":"Test trivia","number":1}')
Add a stub for this method using Mockito's 'when' API, or generate the mock for MockSharedPreferences with 'returnNullOnMissingStub: true'.
The error refer to this line of code.
local_data_source_test.dart
test('should call sharedPreferences to cache the data', () {
dataSource.cacheLastNumberTrivia(tNumberTriviaModel);
final expectedJsonString = jsonEncode(tNumberTriviaModel.toJson());
verify(mockSharedPreferences.setString(
cachedNumberTrivia, expectedJsonString));
});
local_data_source.dart
#override
Future<void> cacheLastNumberTrivia(NumberTriviaModel triviaToCache) {
return sharedPreferences.setString(
cachedNumberTrivia, jsonEncode(triviaToCache.toJson()));
}
It show that method setString from the mocked sharedPreferences is missing. I already run the pub command to generate the mocks. I also have some test case in the file that use the getString method. And it works fine.
Is there something I'm missing so I can't use the setString method? Or are there any solution to this problem?
Thx in advance.
I actually figured it out, sorry for not posting the answer immediately.
I found myself forgot to call the stub for the setString method. Here is the code.
group('cacheNumberTrivia', () {
const tNumberTriviaModel =
NumberTriviaModel(number: 1, text: 'Test trivia');
test('should call sharedPreferences to cache the data', () async {
when(mockSharedPreferences.setString(any, any))
.thenAnswer((_) async => true);
dataSource.cacheLastNumberTrivia(tNumberTriviaModel);
final expectedJsonString = jsonEncode(tNumberTriviaModel.toJson());
verify(mockSharedPreferences.setString(
cachedNumberTrivia, expectedJsonString));
});
});
The stub was actually this line:
when(mockSharedPreferences.setString(any, any))
.thenAnswer((_) async => true);
I forgot to call the when method before verifying the result with verify, hence it throws an error.

The type 'StateNotifierProvider' is declared with 2 type parameters, but 1 type arguments were given

In the context of a Flutter 2.0.5 app whose state I'd like to manage with Riverpod, I thought I can declare a StateNotifierProvider like this:
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateNotifierProvider<CounterStateNotifier>((ref) => CounterStateNotifier());
class CounterStateNotifier extends StateNotifier<int> {
CounterStateNotifier([int count = 0]) : super(count);
void increment() => state++;
}
But Android Studio (and later the Dart compiler as well) complains about the line where I declare the counterProvider variable:
The type 'StateNotifierProvider' is declared with 2 type parameters, but 1 type arguments were given.
Removing the <CounterStateNotifier> type parameter in StateNotifierProvider<CounterStateNotifier> removes the error. However, attempting to read the provider and call its increment method (setting () => context.read(counterProvider).increment() as the onPressed of an ElevatedButton, then pressing the button) gives the following runtime error:
'increment'
method not found
Receiver: 0
Arguments: []
Why is context.read(counterProvider) returning the int state instead of the notifier? And what is the reason behind the type parameter error mentioned in the first part of my question?
I should mention that I'm running my app on the web (with flutter run -d Chrome).
As of Riverpod 0.14.0, State is the default value exposed by StateNotifierProvider.
The syntax for declaring your StateNotifierProvider is now as follows:
final counterProvider = StateNotifierProvider<CounterStateNotifier, int>((ref) => CounterStateNotifier());
Accessing functions now requires adding .notifier (accessing the StateNotifier itself):
context.read(counterProvider.notifier).increment();
And like you've noticed, you now access the state like so:
final count = context.read(counterProvider);
More on the changes here.
You may also use dynamic to accept any type if value for the StateNotifierProvider
final modelProvider =
StateNotifierProvider.autoDispose<ModelClassName, dynamic>(
(ref) => ModelClassName());

Test Flutter Facebook Firebase Login

I would like to test the facebookLogin() method on my app, but I can't set it properly.
Code reference
This is my test :
test(
"should return FirebaseUser when called loginWithFacebook",
() async {
// arrange
when(mockFacebookLogin.login()).thenAnswer((realInvocation) async => tToken);
when(FacebookAuthProvider.credential("token-id")).thenReturn(tCredential);
// act
final result = await repository.loginWithFacebook();
// assert
expect(result, Right(tFirebaseUser));
verify(mockFacebookLogin.login());
},
);
I get this error: Bad state: No method stub was called from within 'when()'. Was a real method called, or perhaps an extension method? on when(FacebookAuthProvider.credential("token-id")).thenReturn(tCredential);
I read that this error shows because I'm trying to mock a static method and I need to convert it to a non-static method.

Invoking Actions from Moq

I've got a service with a method that takes two Actions, one for success and one for failure. Each Action takes a Result parameter that contains additional information...
void AuthoriseUser(AuthDetails loginDetails,
Action<AuthResult> onSuccess,
Action<AuthResult> onFailure);
I'm writing a unit test for a class that is dependant on that service, and I want to test that this class does the correct things in the onSuccess(...) and onFailure(...) callbacks. These are either private or anonymous methods, so how do I setup the mocked service to call either Action?
You can use the Callback method (see also in the Moq quickstart Callbacks section) to configure a callback which gets called with the original arguments of the mocked method call (AuthoriseUser) so you can call your onSuccess and onFailure callbacks there:
var moq = new Mock<IMyService>();
moq.Setup(m => m.AuthoriseUser(It.IsAny<AuthDetails>(),
It.IsAny<Action<AuthResult>>(),
It.IsAny<Action<AuthResult>>()))
.Callback<AuthDetails, Action<AuthResult>, Action<AuthResult>>(
(loginDetails, onSuccess, onFailure) =>
{
onSuccess(new AuthResult()); // fire onSuccess
onFailure(new AuthResult()); // fire onFailure
});