type 'Null' is not a subtype of type 'Future<bool>' - flutter

I'm getting the below error while I'm trying to implement bloc testing in my flutter project
type 'Null' is not a subtype of type 'Future<bool>'
package:mynovatium/features/signup/repositories/signup_repository.dart 10:16 MockRepository.createAccountsignup
Following are the corresponding files that might help identify the cause of the error
signup_bloc_test.dart
class MockRepository extends Mock implements SignUpRepository {}
void main() async {
await configureInjection(inj.Environment.test);
group('SignupBloc', () {
late SignUpBloc signUpBloc;
late SignUpRepository signupRepositoryMock;
setUp(() {
signupRepositoryMock = MockRepository();
signUpBloc = SignUpBloc(signUpRepository: signupRepositoryMock);
});
test('initial state of the bloc is [AuthenticationInitial]', () {
expect(SignUpBloc(signUpRepository: signupRepositoryMock).state,
SignupInitial(),);
});
group('SignUpCreateAccount', () {
blocTest<SignUpBloc, SignUpState>(
'emits [SignUpCreateAccountLoading, SignupInitial] '
'state when successfully Signed up',
setUp: () {
when(signupRepositoryMock.createAccount(
'Nevil',
'abcd',
'nikunj#gmail.com',
'english',
),).thenAnswer((_) async => Future<bool>.value(true));
},
build: () => SignUpBloc(signUpRepository: signupRepositoryMock),
act: (SignUpBloc bloc) => bloc.add(
const SignUpCreateAccount(
'Nevil',
'abcd',
'nikunj#gmail.com',
'english',
),
),
expect: () => [
SignUpCreateAccountLoading(),
SignupInitial(),
],
);
});
});
}
signup_repository.dart
This is the code for the signup repository.
class SignUpRepository {
Future<bool> createAccount(String _firstName, String _lastName, String _eMailAddress, String _language) async {
final Response _response;
try {
_response = await CEApiRequest().post(
Endpoints.createCustomerAPI,
jsonData: <String, dynamic>{
'firstName': _firstName,
'lastName': _lastName,
'email': _eMailAddress,
'language': _language,
'responseUrl': Endpoints.flutterAddress,
},
);
final Map<String, dynamic> _customerMap = jsonDecode(_response.body);
final CustomerModel _clients = CustomerModel.fromJson(_customerMap['data']);
if (_clients.id != null) {
return true;
} else {
return false;
}
} on KBMException catch (e) {
final KBMException _exception = e;
throw _exception;
}
}
}
If anyone has any ideas on what might be the issue here, please help!!

Okay so in the above code you need to stub the methods within the mock repository as well and override it to have it return something incase null is being returned.
class MockRepository extends Mock implements SignUpRepository {
#override
Future<bool> createAccount(String? _firstName, String? _lastName, String? _eMailAddress, String? _language) =>
super.noSuchMethod(Invocation.method(#createAccount, [_firstName, _lastName, _eMailAddress, _language]),
returnValue: Future<bool>.value(false),);
}
Doing something like that done in the above code works well.

Related

FakeUsedError: 'execute' No Stub was found

I'm using mockito for testing, riverpod for state management. I'm trying to test the method in my controller class but getting the FakeUsedError:
FakeUsedError: 'execute' No stub was found which matches the argument
of this method call: execute(Instance of 'AuthUseCaseInput').
I'm calling AuthUseCase class method from the AuthController class.
class AuthController extends StateNotifier<AuthState> {
final AuthUseCase authUseCase;
AuthController(this.authUseCase) : super(const AuthState.initial());
Future<void> mapAuthEventToAuthState(AuthEvent event) async {
state = const AuthState.loading();
await event.map(
signInWithEmailAndPassword: (signInWithEmailAndPassword) async {
final result = await authUseCase.execute(AuthUseCaseInput(
signInWithEmailAndPassword.email,
signInWithEmailAndPassword.password));
await result.fold(
(failure) async => state = AuthState.error(failure),
(login) async => state = const AuthState.loggedIn(),
);
});
}
The test class code is given below
void main() {
late AuthUseCase mockAuthUseCase;
late Login login;
late AuthUseCaseInput authUseCaseInput;
late AuthController authController;
setUpAll(() {
mockAuthUseCase = MockAuthUseCase();
login = LoginModel.fromJson(
json.decode(
jsonReader('helpers/dummy_data/login_success_response.json'),
),
).toEntity();
authUseCaseInput = AuthUseCaseInput(email, password);
when(mockAuthUseCase.execute(authUseCaseInput)).thenAnswer(
(_) async => Right(login),
);
authController = AuthController(mockAuthUseCase);
});
group('Auth Controller', () {
stateNotifierTest<AuthController, AuthState>(
'[AuthState.loggedIn] when sign in is success',
setUp: () async {
when(mockAuthUseCase.execute(authUseCaseInput))
.thenAnswer(
(_) async => Right(login),
);
},
actions: (notifier) => notifier.mapAuthEventToAuthState(
const SignInWithEmailAndPassword(email, password)),
expect: () => [const AuthState.loading(), const AuthState.loggedIn()],
build: () {
return authController;
});
});
}

Flutter autoDispose riverpod StateNotifierProvider

This is my shared riverpod class that i want to use that on multiple screens, but after navigate to another screen using ref.listen couldn't dispose or cancel and using another ref.listen work twice, how can i cancel each ref.listen on screen which i used that? for example you suppose i have two screen A and B and into A screen i have
A screen
final future = ref.watch(requestProvider);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {});
on this ref.listen i navigate to another screen when server return 200 ok? now in B screen which i have ref.listen again:
B screen
final future = ref.watch(requestProvider);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {});
without sending any request to server this listener work and listen to previous listener
requestProvider on this class shared between multiple screens and autoDispose don't work for that, because after creating another StateNotifierProvider such as requestProviderA_Screen work fine without problem, for example:
final requestProvider = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
final requestProviderA_Screen = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
my request riverpod class:
final requestRepositoryProvider =
Provider.autoDispose<Repository>((ref) => Repository(ref.read));
final requestProvider = StateNotifierProvider.autoDispose<RequestNotifier,
NetworkRequestState<int?>>(
(ref) => RequestNotifier(ref.watch(requestRepositoryProvider)));
class Repository {
final Reader _reader;
Repository(this._reader);
Future<int?> getResponse(
HTTP method, String endPoint, Map<String, dynamic> parameters) async {
try {
const r = RetryOptions(maxAttempts: 3);
final response = await r.retry(
() => _submit(method, endPoint, parameters),
retryIf: (e) => e is SocketException || e is TimeoutException,
);
return response.statusCode;
} on DioError catch (e) {
throw (e.response != null
? e.response!.statusCode
: e.error.osError.errorCode) as Object;
}
}
Future<Response> _submit(
HTTP method, String endPoint, Map<String, dynamic> parameters) {
final Options options = Options(
headers: {'Content-Type': 'application/json'},
);
late Future<Response> _r;
switch (method) {
case HTTP.GET:
_r = _reader(dioProvider).get(
endPoint,
queryParameters: parameters,
options: options,
);
break;
case HTTP.POST:
_r = _reader(dioProvider).post(
endPoint,
queryParameters: parameters,
options: options,
);
break;
}
return _r.timeout(const Duration(seconds: 30));
}
}
class RequestNotifier extends RequestStateNotifier<int?> {
final Repository _repository;
RequestNotifier(this._repository);
Future<NetworkRequestState<int?>> send({
required HTTP method,
required String endPoint,
required Map<String, dynamic> parameters,
}) =>
makeRequest(
() => _repository.getResponse(method, endPoint, parameters));
}
and one of screen which i use this class:
class SignUp extends HookConsumerWidget {
final String mobileNumber;
const SignUp({Key? key, required this.mobileNumber}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final _formKey = useMemoized(() => GlobalKey<FormState>());
final _nameFamily = useTextEditingController();
final future = ref.watch(requestProvider);
useEffect(() {
_nameFamily.dispose();
}, [_nameFamily]);
ref.listen<NetworkRequestState<int?>>(requestProvider, (
NetworkRequestState? previousState,
NetworkRequestState newState,
) {
newState.when(
idle: () {},
//...
}
success: (status) {
//...
Routes.seafarer.navigate(
'/complete-register',
params: {
'mobile_number': mobileNumber.trim(),
'name_family': _nameFamily.text.trim()
},
);
},
error: (error, stackTrace) {
//...
});
});
final _onSubmit = useMemoized(
() => () {
if (_nameFamily.text.trim().isEmpty) {
//...
} else {
//..
ref.read(requestProvider.notifier).send(
method: HTTP.GET,
endPoint: Server.$updateNameFamily,
parameters: {
'mobile_number': mobileNumber,
'name_family': _nameFamily.text.trim()
});
}
},
[_formKey],
);
return Scaffold(
//...
);
}
}

Testing bloc events type 'Null' is not a subtype of type

I am trying to learn bloc and I am writing simple unit tests. I am trying to test the auth events but I am facing the error below. Inside my app when I trigger an event, I don't get any errors and everything seems to work fine, so why am I getting error here? Am I missing something, could anyone advise?
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final AuthenticationRepository _authRepository;
late StreamSubscription<AuthStatus> _authSubscription;
AuthenticationBloc(
{required AuthenticationRepository authenticationRepository})
: _authRepository = authenticationRepository,
super(const AuthenticationState()) {
on<AuthStateChanged>(_onAuthStatusChanged);
on<AuthenticationLogoutRequested>(_onLogoutRequested);
_authSubscription = _authRepository.status
.listen((status) => add(AuthStateChanged(authStatus: status)));
}
enum AuthStatus { unknown, authenticated, unauthenticated }
class AuthenticationRepository {
final _controller = StreamController<AuthStatus>();
Stream<AuthStatus> get status => _controller.stream;
Future<void> logIn({
required String username,
required String password,
}) async {
await Future.delayed(
const Duration(milliseconds: 300),
() => _controller.add(AuthStatus.authenticated),
);
}
void logOut() {
_controller.add(AuthStatus.unauthenticated);
}
void dispose() => _controller.close();
}
class AuthenticationState extends Equatable {
final AuthStatus status;
final User? user;
const AuthenticationState({this.status = AuthStatus.unknown, this.user});
#override
List<Object?> get props => [user, status];
}
void main() {
late AuthenticationBloc authenticationBloc;
MockAuthenticationRepository authenticationRepository = MockAuthenticationRepository();
setUp((){
authenticationBloc = AuthenticationBloc(authenticationRepository: authenticationRepository);
});
group('AuthenticationEvent', () {
group('Auth status changes', () {
test('User is unknown', () {
expect(authenticationBloc.state.status, AuthStatus.unknown);
});
test('User is authorized', () {
authenticationBloc.add(AuthStateChanged(authStatus: AuthStatus.authenticated));
expect(authenticationBloc.state.status, AuthStatus.authenticated);
});
test('User is unauthorized', () {
authenticationBloc.add(AuthStateChanged(authStatus: AuthStatus.unauthenticated));
expect(authenticationBloc.state.status, AuthStatus.unknown);
});
});
});
}
It is most likely that you have not created a stub for a function in your mock class. This is generally the cause of the NULL return. This can be achieved by using when() from the mockito package. https://pub.dev/packages/mockito.
Also there is a bloc testing package that may be useful to look into. https://pub.dev/packages/bloc_test
Below is an example of how I implemented it. Hope this helps.
blocTest<ValidateUserBloc, ValidateUserState>('Validate User - Success',
build: () {
when(mockSettingsRepo.validateUser(url, authCode)).thenAnswer(
(_) async => User(
success: true, valid: true, url: url, authCode: authCode));
return validateUserBloc;
},
seed: () => ValidateUserState(user: User(url: url, authCode: authCode)),
act: (bloc) => bloc.add(ValidateUserAuthoriseEvent()),
expect: () => <ValidateUserState>[
ValidateUserState(
status: ValidUserStatus.success,
user: User(
success: true, valid: true, url: url, authCode: authCode))
]);

Dart - getting confused by test result

Apologies if this is a totally noob question, but I can't find a way through the following problem:
Test result:
Expected: <Instance of 'Future< SignUpResultSuccess >'>
Actual: <Instance of 'Future< SignUpResult >'>
I was expecting the actual result to be a 'Future< SignUpResultSuccess >' rather than a 'Future< SignUpResult >'.
Here's the code:
import 'package:equatable/equatable.dart';
import '../data/authentication_data_provider.dart';
abstract class SignUpResult {}
class SignUpResultSuccess extends Equatable implements SignUpResult {
const SignUpResultSuccess();
#override
List<Object?> get props => [];
}
class SignUpResultFailure extends Equatable implements SignUpResult {
const SignUpResultFailure({String? errorCode}) : _errorCode = errorCode ?? '';
final String _errorCode;
String get errorCode => _errorCode;
#override
List<Object?> get props => [_errorCode];
}
class AuthenticationRepository {
AuthenticationRepository({
AuthenticationDataProvider? authenticationDataProvider,
}) : _authenticationDataProvider =
authenticationDataProvider ?? AuthenticationDataProvider();
final AuthenticationDataProvider _authenticationDataProvider;
Future<SignUpResult> signUp({
required String email,
required String password,
}) async {
try {
await _authenticationDataProvider.createEmailAccount(
email: email,
password: password,
);
return const SignUpResultSuccess();
} on CreateEmailAccountException catch (createEmailAccountException) {
return SignUpResultFailure(
errorCode: createEmailAccountException.errorCode,
);
} catch (e) {
return const SignUpResultFailure();
}
}
}
And here is the test:
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pocket_open_access/app/features/authentication/data/authentication_data_provider.dart';
import 'package:pocket_open_access/app/features/authentication/repository/authentication_repository.dart';
class MockAuthenticationDataProvider extends Mock
implements AuthenticationDataProvider {}
void main() {
late AuthenticationRepository authenticationRepository;
late AuthenticationDataProvider mockAuthenticationDataProvider;
const String email = 'test#test.com';
const String password = '1234567';
setUp(
() {
mockAuthenticationDataProvider = MockAuthenticationDataProvider();
authenticationRepository = AuthenticationRepository(
authenticationDataProvider: mockAuthenticationDataProvider,
);
},
);
group(
'Sign-up user:',
() {
test(
'is successful so returns SignUpResultSuccess()',
() {
when(() => mockAuthenticationDataProvider.createEmailAccount(
email: any(named: 'email'),
password: any(named: 'password'),
)).thenAnswer(
(_) async {},
);
expect(
authenticationRepository.signUp(email: email, password: password),
Future.value(const SignUpResultSuccess()));
},
);
test(
'fails so returns SignUpResultFailure with error code',
() {
when(
() => mockAuthenticationDataProvider.createEmailAccount(
email: any(named: 'email'),
password: any(named: 'password'),
),
).thenThrow(CreateEmailAccountException(errorCode: 'error-code'));
expect(
authenticationRepository.signUp(email: email, password: password),
Future.value(const SignUpResultFailure(errorCode: 'error-code')));
},
);
},
);
}
Thanks #jamesdlin. I used the following which now works :-)
test(
'is successful so returns SignUpResultSuccess()',
() async {
when(() => mockAuthenticationDataProvider.createEmailAccount(
email: any(named: 'email'),
password: any(named: 'password'),
)).thenAnswer(
(_) async {},
);
const SignUpResultSuccess expected = SignUpResultSuccess();
final SignUpResult actual = await authenticationRepository.signUp(
email: email, password: password);
expect(actual, expected);
},
);

Flutter test GraphQL query

I want to test my GraphQL Query. I have my GraphQL client, and I use a remote datasource to do my requests.
class MockGraphQLClient extends Mock implements GraphQLClient {}
void main() {
RemoteDataSource RemoteDataSource;
MockGraphQLClient mockClient;
setUp(() {
mockClient = MockGraphQLClient();
RemoteDataSource = RemoteDataSource(client: mockClient);
});
group('RemoteDataSource', () {
group('getDetails', () {
test(
'should preform a query with get details with id variable',
() async {
final id = "id";
when(
mockClient.query(
QueryOptions(
documentNode: gql(Queries.getDetailsQuery),
variables: {
'id': id,
},
),
),
).thenAnswer((_) async => QueryResult(
data: json.decode(fixture('details.json'))['data'])));
await RemoteDataSource.getDetailsQuery(id);
verify(mockClient.query(
QueryOptions(
documentNode: gql(Queries.getDetailsQuery),
variables: {
'id': id,
},
),
));
});
});
});
}
I would like to know how to mock the response of my query. Currently it does not return a result, it returns null
But I don't understand why my query returns null, although I have mocked my client, and in my "when" method I use a "thenAnwser" to return the desired value
final GraphQLClient client;
ChatroomRemoteDataSource({this.client});
#override
Future<Model> getDetails(String id) async {
try {
final result = await client.query(QueryOptions(
documentNode: gql(Queries.getDetailsQuery),
variables: {
'id': id,
},
)); // return => null ????
if (result.data == null) {
return [];
}
return result.data['details']
} on Exception catch (exception) {
throw ServerException();
}
}
The argument on which when should mock an answer for is quite complex. You might be easier to just use any in your test case.
when(mockClient.query(any)).thenAnswer((_) async => QueryResult(
data: json.decode(fixture('details.json'))['data'])));
any is provided by Mockito to match any argument.
In the
graphql_flutter: ^5.0.0
you need the add source as null or QueryResultSource.network, when call method when can you pass any so you don't need to pass QueryOptions( documentNode: gql(Queries.getDetailsQuery), variables: { 'id': id, }, ),
here is final code:
when(mockClient.query(any)).thenAnswer((_) async => QueryResult( data: json.decode(fixture('details.json'))['data'], ,source: null)));
any is not accepted with graphQLClient.query(any)) as it accepts non nullable QueryOptions<dynamic>
Using mockito: ^5.1.0 , you will get the warning: The argument type 'Null' can't be assigned to the parameter type 'QueryOptions<dynamic>'
I solved it by creating the mocked QueryOptions as:
class SutQueryOption extends Mock implements QueryOptions {}
void main() {
SutQueryOption _mockedQueryOption;
....
setUp(() {
SutQueryOption _mockedQueryOption = MockedQueryOptions();
....
});
when(mockClient.query(_mockedQueryOption)).thenAnswer((_) async => ....