I'm trying to run a test with mockito following an outdated tutorial for a messaging app in flutter. I'm trying to use the most updated versions of everything. The tutorial just implemented flutter bloc, and now I'm getting an error that I don't know how to fix.
This is the test:
import 'package:chat/chat.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:rethink_chat/states_management/message/message_bloc.dart';
import 'message_bloc_test.mocks.dart';
#GenerateMocks([IMessageService])
void main() {
late MessageBloc sut;
late MockIMessageService messageService; //changed to Mock
late User user;
setUp(() {
messageService = MockIMessageService(); // FakeMessageService();
user = User(
username: 'test',
photoUrl: '',
active: true,
lastseen: DateTime.now(),
);
sut = MessageBloc(messageService);
});
tearDown(() => sut.close());
test('should emit message sent state when message is sent', () {
final message = Message(
from: '123',
to: '456',
contents: 'test message',
timestamp: DateTime.now(),
);
when(messageService.send(message)).thenAnswer((_) async => true);
when(messageService.dispose()).thenAnswer((_) async => <Object?>[]);
sut.add(MessageEvent.onMessageSent(message));
expectLater(sut.stream, emits(MessageState.sent(message)));
});
}
This is the error:
Bad state: add(MessageSent) was called without a registered event handler.
Make sure to register a handler via on<MessageSent>((event, emit) {...})
This is message_event.dart:
part of 'message_bloc.dart';
abstract class MessageEvent extends Equatable {
const MessageEvent();
factory MessageEvent.onSubscribed(User user) => Subscribed(user);
factory MessageEvent.onMessageSent(Message message) => MessageSent(message);
#override
List<Object> get props => [];
}
class Subscribed extends MessageEvent {
final User user;
const Subscribed(this.user);
#override
List<Object> get props => [user];
}
class MessageSent extends MessageEvent {
final Message message;
const MessageSent(this.message);
#override
List<Object> get props => [message];
}
class _MessageReceived extends MessageEvent {
const _MessageReceived(this.message);
final Message message;
#override
List<Object> get props => [message];
}
And message_bloc.dart:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:chat/chat.dart';
import 'package:equatable/equatable.dart';
//import 'package:rethink_chat/states_management/message/message_event.dart';
// import 'package:rethink_chat/states_management/message/message_state.dart';
//import 'package:chat/chat.dart';
part 'message_event.dart';
part 'message_state.dart';
class MessageBloc extends Bloc<MessageEvent, MessageState> {
final IMessageService _messageService;
StreamSubscription?
_subscription; // added a ? but not sure if it's neutral, helpful or harmful
MessageBloc(this._messageService) : super(MessageState.initial());
#override
Stream<MessageState> mapEventToState(MessageEvent event) async* {
if (event is Subscribed) {
await _subscription?.cancel();
_subscription = _messageService
.messages(activeUser: event.user)
.listen((message) => add(_MessageReceived(message)));
}
if (event is _MessageReceived) {
yield MessageState.received(event.message);
}
if (event is MessageSent) {
await _messageService.send(event.message);
yield MessageState.sent(event.message);
}
}
#override
Future<void> close() {
_subscription?.cancel();
_messageService.dispose();
return super.close();
}
}
I saw in another post that flutter bloc at some point removed mapEventToState, but I don't know how to fix this for my situation? I'm so new to this that I have trouble knowing what to fix for this particular code, based on seeing examples of other code.
Please let me know if any other information would be helpful and I'll update this post to include it. I'm a total beginner, so this is hopefully a very simple fix that I'm overlooking. I greatly appreciate any help.
Edit: I also get a similar error for a similar test:
test('should emit messages received from service', () {
final message = Message(
from: '123',
to: '456',
contents: 'test message',
timestamp: DateTime.now(),
);
when(messageService.messages(activeUser: anyNamed('activeUser')))
.thenAnswer((_) => Stream.fromIterable([message]));
when(messageService.dispose()).thenAnswer((_) async => <Object?>[]);
sut.add(MessageEvent.onSubscribed(user));
expectLater(sut.stream, emitsInOrder([MessageReceivedSuccess(message)]));
});
The error is
Bad state: add(Subscribed) was called without a registered event handler.
Make sure to register a handler via on<Subscribed>((event, emit) {...})
I assume the fix should be the same for both, right?
Edit2: so I'm thinking I need to put on<MessageSent>((event, emit) {...}) and on<Subscribed>((event, emit) {...}) somewhere in the code, but where?
Check out the migration guide https://bloclibrary.dev/#/migration
It looks like your code is still version 7.1 or lower. You first need to follow the migration from 7.1 to 7.2. This will show you how to migrate your mapEventToState to the new on<Event> format.
Related
I am trying to fix an issue related to Flutter Bloc. I am editing someone else code to make it work with the latest flutter_bloc version but I am unable to do so. Can someone do a rewrite for my code so I can run it? I saw many answers but I am unable to understand how to fix my own code.
This is the complete code for all_categories_bloc.dart
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Code for all_categories_event.dart
abstract class AllCategoriesEvent extends Equatable {
AllCategoriesEvent();
}
class GetAllCategories extends AllCategoriesEvent {
#override
List<Object> get props => null;
}
Code for all_categories_state.dart
abstract class AllCategoriesState extends Equatable {
const AllCategoriesState();
}
class AllCategoriesInitial extends AllCategoriesState {
AllCategoriesInitial();
#override
List<Object> get props => [];
}
class AllCategoriesLoading extends AllCategoriesState {
const AllCategoriesLoading();
#override
List<Object> get props => null;
}
class AllCategoriesLoaded extends AllCategoriesState {
final CategoriesModel categoriesModel;
const AllCategoriesLoaded(this.categoriesModel);
#override
List<Object> get props => [categoriesModel];
}
class AllCategoriesError extends AllCategoriesState {
final String message;
const AllCategoriesError(this.message);
#override
List<Object> get props => [message];
}
It throws an error "Bad state: add(GetAllCategories) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})"
I have this add(GetAllCategories) in my home. dart file but the solution is to edit this code which I am unable to do so. Can someone do a rewrite for the latest bloc? I would be thankful.
Let's get through the migration guide step by step:
package:bloc v5.0.0: initialState has been removed. For more information check out #1304.
You should simply remove the AllCategoriesState get initialState => AllCategoriesInitial(); portion from your BLoC.
package:bloc v7.2.0 Introduce new on<Event> API. For more information, read the full proposal.
As a part of this migration, the mapEventToState method was removed, each event is registered in the constructor separately with the on<Event> API.
First of all, register your events in the constructor:
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
Then, create the _onGetAllCategories method:
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
Notice, that instead of using generators and yielding the next state, you should use the Emitter<AllCategoriesState> emitter.
Here is the final result of the migrated AllCategoriesBloc:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository _apiRepository = ApiRepository();
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Bonus tip
Instead of creating an instance of ApiRepository inside the BLoC directly, you can use the constructor injection:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
required this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
...
}
Now, when creating BLoC, pass the instance of the repository to the constructor, like AllCategoriesBloc(apiRepository: ApiRepository()). This way you will be able to properly unit test your BLoC by mocking dependencies (in this case, ApiRepository).
I'm working in a team and we are using flutter bloc for state management, however one of our test cases just doesn't make any sense at all. The bloc itself works fine but the test is what's not making any sense.
Below is the bloc itself.
import 'package:vaccify/features/logout/logout.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_name.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_profile_pic_url.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
GetUserName getUserName;
GetUserProfilePic getUserProfilePic;
Logout logout;
AuthBloc(
{required this.getUserName,
required this.logout,
required this.getUserProfilePic})
: super(NotLoggedIn()) {
on<AuthLogIn>(_onLogIn);
on<AuthLogOut>(_onLogOut);
}
void _onLogIn(
AuthLogIn event,
Emitter<AuthState> emit,
) async {
final name = await getUserName();
final profilePic = await getUserProfilePic();
emit(LoggedIn(name, profilePic));
}
void _onLogOut(
AuthLogOut event,
Emitter<AuthState> emit,
) {
logout();
emit(NotLoggedIn());
}
}
And now for the test
import 'package:bloc_test/bloc_test.dart';
import 'package:vaccify/core/bloc/authentication/auth_bloc.dart';
import 'package:vaccify/features/logout/logout.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_name.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_profile_pic_url.dart';
class MockGetUserName extends Mock implements GetUserName {}
class MockGetProfilePicUrl extends Mock implements GetUserProfilePic {}
class MockLogout extends Mock implements Logout {}
void main() {
late AuthBloc authBloc;
late MockGetUserName mockGetUserName;
late MockLogout mockLogout;
late MockGetProfilePicUrl mockGetProfilePicUrl;
setUp(() {
mockGetUserName = MockGetUserName();
mockLogout = MockLogout();
mockGetProfilePicUrl = MockGetProfilePicUrl();
authBloc = AuthBloc(
getUserName: mockGetUserName,
logout: mockLogout,
getUserProfilePic: mockGetProfilePicUrl);
});
/// Contains bloc tests for the [AuthBloc] class
group(
'AuthBloc',
() {
/// Tests that a [LoggedIn] state occurs when the [AuthLogIn] is called.
blocTest(
'login',
build: () {
when(() => mockGetUserName()).thenAnswer((_) async => "Hello");
when(() => mockGetProfilePicUrl()).thenAnswer((_) async => "");
return authBloc;
},
act: (AuthBloc bloc) {
bloc.add(AuthLogIn());
},
expect: () => [isA<LoggedIn>()],
);
/// Tests that a [NotLoggedIn] state occurs when the [AuthLogOut] is called.
blocTest(
'logout',
build: () {
when(() => mockGetUserName()).thenAnswer((_) async => "John Smith");
when(() => mockGetProfilePicUrl()).thenAnswer((_) async => "");
when(() => mockLogout()).thenAnswer((_) async {});
return authBloc;
},
act: (AuthBloc bloc) {
bloc.add(AuthLogIn());
bloc.add(AuthLogOut());
},
expect: () => [
isA<LoggedIn>(),
isA<NotLoggedIn>(),
],
);
},
);
}
The first test 'login' works fine and passes because we add one event "AuthLogin()" and then we expect LoggedIn state
The second test 'logout' is the problem because we are adding two events AuthLogin() & AuthLogOut() because you have to be logged in to be able to logout.
Then we expect LoggedIn & NotLoggedIn states respectively.
The test fails with the following message
Expected: [<<Instance of 'LoggedIn'>>, <<Instance of 'NotLoggedIn'>>]
Actual: [Instance of 'NotLoggedIn', Instance of 'LoggedIn']
Interestingly when we swap the two expects the test passes...Like below
expect: () => [
isA<NotLoggedIn>(),
isA<LoggedIn>(),
],
Any advice or guidance will be greatly appreciated.
Thanks all
If you're adding AuthLogIn event just to prepare your bloc for the second event, instead of that you can use seed from blocTest. Something like this:
blocTest(
'logout',
build: () {
when(() => mockLogout()).thenAnswer((_) async {});
return authBloc;
},
seed: () => LoggedIn(),
act: (AuthBloc bloc) {
bloc.add(AuthLogOut());
},
expect: () => [
isA<NotLoggedIn>(),
],
);
And about your problem, I think using a delay between adding two events (await Future.delayed(Duration(seconds: 1))) will fix it. Note that Bloc transforms events concurrently by default and in your case the log out is being processed first (it finishes faster than log in function) and then your log in event is being processed which causes the problem.
I am new in this state management world. So I was trying to follow this tutorial (How to Save Products in a Wishlist using the BloC Pattern - EP10 - The eCommerce Series), but mapEventToState is deprecated so I am not sure what to do.
Here is my state:
part of 'wishlist_bloc.dart';
abstract class WishlistState extends Equatable {
const WishlistState();
#override
List<Object> get props => [];
}
class WishlistLoading extends WishlistState {}
class WishlistLoaded extends WishlistState {
final WishlistModel wishlist;
const WishlistLoaded({this.wishlist = const WishlistModel()});
#override
List<Object> get props => [wishlist];
}
class WishlistError extends WishlistState {}
Here is my event:
part of 'wishlist_bloc.dart';
abstract class WishlistEvent extends Equatable {
const WishlistEvent();
#override
List<Object> get props => [];
}
class StartWishlist extends WishlistEvent {}
class AddWishlistProduct extends WishlistEvent {
final ProductModel product;
const AddWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
class RemoveWishlistProduct extends WishlistEvent {
final ProductModel product;
const RemoveWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
Here is my bloc:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(event, emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
// Error ...
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(event, emit) async {}
}
But I get this error instead: "The getter 'wishlist' isn't defined for the type 'WishlistState'. Try importing the library that defines 'wishlist', correcting the name to the name of an existing getter, or defining a getter or field name 'wishlist'".
How to access 'wishlist' in the new version of flutter_bloc? Thank you.
Try to define arguments types while difining the functions. So your updated code would look like this:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(
// Added argument types below
StartWishlist event, Emitter<WishlistState> emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
void _mapAddWishlistToState(
// Added argument types below
AddWishlistProduct event, Emitter<WishlistState> emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(
// Added argument types below
RemoveWishlistProduct event, Emitter<WishlistState> emit) async {}
}
Type promotion is impossible for properties, since they can potentially return different values each time they are called. As such, it is impossible for the compiler to know that the state getter will return a WishlistLoaded instance, even after knowing that the same getter returned a WishlistLoaded four lines earlier.
One way around this is to assign the state to a local variable, which is eligible for type promotion.
void _mapAddWishlistToState(AddWishlistProduct event, Emitter<WishlistState> emit) async {
final state = this.state; // local variable
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
The linked video used a parameter, which is also eligible for type promotion. If it had used the state getter directly in _mapAddWishlistProductToState, it would have run into the same error.
You just need to cast your state as following
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from((state as WishlistLoaded).wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
I'm a beginner with tdd so please forgive me if it's a dumb question.
I'm having difficulty unit testing GetxControllers. Does anyone know a simple way of doing this?
Whenever I do I get errors since Get is calling onStart and it doesn't like the result Mockito's giving it. I've tried using Mockito 5.0.1's auto generated code as well as the older syntax, class MockController extends Mock implements Controller{}, as well as extends Fake.
The auto generated code has build errors, since Mockito is trying to use _InternalFinalCallback, but it's not being imported as it's private. I tried just copy pasting that part of the code into my generated file (and switching off pub build watch) but first that's a short term solution with it's own issues, 2nd it still doesn't work since the onStart and onDelete functions now tell me they're not valid overrides.
Also, I can see the get_test package but it's documentation is basically 0, and in the examples the controller is just used directly -- there's never a mocked controller.
I tried setting Get.testMode = true; but again that doesn't seem to do anything. And while I found that property in the docs, I didn't find how to use it correctly.
Any help would be appreciated,
Here's my code but the issue seems to be with the GetxControllers, so I don't think it's relevant much:
class FakeAuthController extends Fake implements AuthController {}
#GenerateMocks([AuthController])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late MockAuthController mockAuthController;
late FakeAuthController fakeAuthController;
late SessionController sessionController;
setUp(() {
Get.testMode = true;
mockAuthController = MockAuthController();
fakeAuthController = FakeAuthController();
Get.put<AuthController>(mockAuthController);
sessionController = SessionController();
});
tearDown(() {
Get.delete<AuthController>();
});
group('getSessionInfo', () {
test('Calls authFacade getSignedInUserId', () async {
await sessionController.getSessionInfo();
when(Get.find<AuthController>()).thenReturn(fakeAuthController);
verify(mockAuthController.getSignedInUserId());
});
});
}
There really isn't anything in my AuthController and session controller, but code is as follows:
import 'package:get/get.dart';
class AuthController extends GetxController {
String getSignedInUserId() {
// await Future.delayed(Duration(milliseconds: 1));
return '1';
}
}
import 'package:get/get.dart';
import '../../auth/controllers/auth_controller.dart';
import '../models/session_info.dart';
class SessionController extends GetxController {
final AuthController authController = Get.find<AuthController>();
Rx<SessionInfo> sessionInfo = Rx<SessionInfo>();
Future<void> getSessionInfo() async {
// authController.getSignedInUserId();
// sessionInfo.value = SessionInfo(userId: userId);
}
}
And the auto-generated, buggy mock controller:
// Mocks generated by Mockito 5.0.1 from annotations
// in smart_locker_controller/test/shared/controllers/session_controller_test.dart.
// Do not manually edit this file.
import 'dart:ui' as _i4;
import 'package:get/get_instance/src/lifecycle.dart' as _i2;
import 'package:get/get_state_manager/src/simple/list_notifier.dart' as _i5;
import 'package:mockito/mockito.dart' as _i1;
import 'package:smart_locker_controller/auth/controllers/auth_controller.dart'
as _i3;
// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis
class _Fake_InternalFinalCallback<T> extends _i1.Fake
implements _i2._InternalFinalCallback<T> {}
/// A class which mocks [AuthController].
///
/// See the documentation for Mockito's code generation for more information.
class MockAuthController extends _i1.Mock implements _i3.AuthController {
MockAuthController() {
_i1.throwOnMissingStub(this);
}
#override
int get notifierVersion =>
(super.noSuchMethod(Invocation.getter(#notifierVersion), returnValue: 0)
as int);
#override
int get notifierMicrotask =>
(super.noSuchMethod(Invocation.getter(#notifierMicrotask), returnValue: 0)
as int);
#override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
#override
int get listeners =>
(super.noSuchMethod(Invocation.getter(#listeners), returnValue: 0)
as int);
#override
_i2._InternalFinalCallback<void> get onStart =>
(super.noSuchMethod(Invocation.getter(#onStart),
returnValue: _Fake_InternalFinalCallback<void>())
as _i2._InternalFinalCallback<void>);
#override
_i2._InternalFinalCallback<void> get onDelete =>
(super.noSuchMethod(Invocation.getter(#onDelete),
returnValue: _Fake_InternalFinalCallback<void>())
as _i2._InternalFinalCallback<void>);
#override
bool get initialized =>
(super.noSuchMethod(Invocation.getter(#initialized), returnValue: false)
as bool);
#override
bool get isClosed =>
(super.noSuchMethod(Invocation.getter(#isClosed), returnValue: false)
as bool);
#override
String getSignedInUserId() =>
(super.noSuchMethod(Invocation.method(#getSignedInUserId, []),
returnValue: '') as String);
#override
void update([List<Object>? ids, bool? condition = true]) =>
super.noSuchMethod(Invocation.method(#update, [ids, condition]),
returnValueForMissingStub: null);
#override
void refreshGroup(Object? id) =>
super.noSuchMethod(Invocation.method(#refreshGroup, [id]),
returnValueForMissingStub: null);
#override
void removeListener(_i4.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null);
#override
void removeListenerId(Object? id, _i4.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#removeListenerId, [id, listener]),
returnValueForMissingStub: null);
#override
_i5.Disposer addListener(_i5.GetStateUpdate? listener) =>
(super.noSuchMethod(Invocation.method(#addListener, [listener]),
returnValue: () {}) as _i5.Disposer);
#override
_i5.Disposer addListenerId(Object? key, _i5.GetStateUpdate? listener) =>
(super.noSuchMethod(Invocation.method(#addListenerId, [key, listener]),
returnValue: () {}) as _i5.Disposer);
#override
void disposeId(Object? id) =>
super.noSuchMethod(Invocation.method(#disposeId, [id]),
returnValueForMissingStub: null);
}
This question has now been answered in the GetX docs.
Pasted from the docs:
Tests
You can test your controllers like any other class, including their lifecycles:
class Controller extends GetxController {
#override
void onInit() {
super.onInit();
//Change value to name2
name.value = 'name2';
}
#override
void onClose() {
name.value = '';
super.onClose();
}
final name = 'name1'.obs;
void changeName() => name.value = 'name3';
}
void main() {
test('''
Test the state of the reactive variable "name" across all of its lifecycles''',
() {
/// You can test the controller without the lifecycle,
/// but it's not recommended unless you're not using
/// GetX dependency injection
final controller = Controller();
expect(controller.name.value, 'name1');
/// If you are using it, you can test everything,
/// including the state of the application after each lifecycle.
Get.put(controller); // onInit was called
expect(controller.name.value, 'name2');
/// Test your functions
controller.changeName();
expect(controller.name.value, 'name3');
/// onClose was called
Get.delete<Controller>();
expect(controller.name.value, '');
});
}
Mockito or mocktail
If you need to mock your GetxController/GetxService, you should extend GetxController, and mixin it with Mock, that way
class NotificationServiceMock extends GetxService with Mock implements NotificationService {}
Not exactly intuitive, is it?
Try changing your fake controller definition to:
class FakeAuthController extends GetxController with Fake implements AuthController {}
Not sure this works for Fake, but I just fixed a similar issue with Mock with it.
I'm really confused of how to get the bloc to emit the initial state. bloc.state emits the latest state. And since there is no #override initialState available in the new bloc library, initialState was passed in to the super constructor. But still bloc does not emit the initialState, which in this case is Empty().
number_trivia_bloc.dart
import 'package:clean_architecture/core/error/failure.dart';
import 'package:clean_architecture/core/utils/input_converter.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_concrete_number_trivia_repository.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'number_trivia_state.dart';
import 'number_trivia_event.dart';
const SERVER_FAILURE = 'Server failure';
const CACHE_FAILURE = 'Cache failure';
const INVALID_INPUT_FAILURE =
'Invalid Input Failure - The input should not be a negative integer or zero';
class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> {
final GetConcreteNumberTrivia getConcreteNumberTrivia;
final GetRandomNumberTrivia getRandomNumberTrivia;
final InputConverter inputConverter;
NumberTriviaBloc({
// Changed the name of the constructor parameter (cannot use 'this.')
#required GetConcreteNumberTrivia concrete,
#required GetRandomNumberTrivia random,
#required this.inputConverter,
// Asserts are how you can make sure that a passed in argument is not null.
// We omit this elsewhere for the sake of brevity.
}) : assert(concrete != null),
assert(random != null),
assert(inputConverter != null),
getConcreteNumberTrivia = concrete,
getRandomNumberTrivia = random,
super(Empty());
#override
Stream<NumberTriviaState> mapEventToState(
NumberTriviaEvent event,
) async* {
if (event is GetTriviaForConcreteNumber) {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
yield* inputEither.fold(
(failure) async* {
yield Error(message: INVALID_INPUT_FAILURE);
},
(integer) async* {
yield Loading();
},
);
}
}
}
number_trivia_bloc_test.dart
import 'package:clean_architecture/core/utils/input_converter.dart';
import 'package:clean_architecture/features/number_trivia/data/models/number_trivia_model.dart';
import 'package:clean_architecture/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_concrete_number_trivia_repository.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_state.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_event.dart';
import 'package:mockito/mockito.dart';
class MockGetConcreteNumberTrivia extends Mock
implements GetConcreteNumberTrivia {}
class MockGetRandomNumberTrivia extends Mock implements GetRandomNumberTrivia {}
class MockInputConverter extends Mock implements InputConverter {}
void main() {
NumberTriviaBloc bloc;
MockGetConcreteNumberTrivia mockGetConcreteNumberTrivia;
MockGetRandomNumberTrivia mockGetRandomNumberTrivia;
MockInputConverter mockInputConverter;
setUp(() {
mockGetConcreteNumberTrivia = MockGetConcreteNumberTrivia();
mockGetRandomNumberTrivia = MockGetRandomNumberTrivia();
mockInputConverter = MockInputConverter();
bloc = NumberTriviaBloc(
concrete: mockGetConcreteNumberTrivia,
random: mockGetRandomNumberTrivia,
inputConverter: mockInputConverter,
);
});
test('initialState should be Empty', () {
// assert
expect(bloc.state, equals(Empty()));
});
group('GetTriviaForNumber', () {
String str = '1';
int parsedStr = int.parse(str);
NumberTrivia tTrivia =
NumberTriviaModel(text: 'test text', number: parsedStr);
test('Should convert a string to an unsigned integer', () async {
//arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Right(parsedStr));
//act
bloc.add(GetTriviaForConcreteNumber(str));
await untilCalled(mockInputConverter.stringToUnsignedInteger(str));
//assert
verify(mockInputConverter.stringToUnsignedInteger(any));
});
test('Should return [Error] for InvalidInputFailure', () async {
//arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Left(InvalidInputFailure()));
//assert later
final expected = [
Empty(),
Error(message: INVALID_INPUT_FAILURE),
];
expectLater(bloc, emitsInOrder(expected));
//act
bloc.add(GetTriviaForConcreteNumber(str));
});
});
}
It should work but all I get is this error screaming at me. I tries bloc.cast(), bloc.asBroadcastStream(). Nothing seems to work.So, please, can someone help me figure this out. Thanks in advance.
There contains some change from flutter_bloc 6.0.0
You can check the initial state via
test('initial state is correct', () {
expect(bloc.state, Empty());
});
Mentioned here:
Regression: initial state is not emited anymore