I am trying to do a very simple blocTest to test initial state. But getting below error. Interestingly I do have another working bloc and test in the same project. I checked line by line to see any mismatch, but everything looks perfect, except the 'act', which I am doing in the other, not here, assuming not relevant for this initial state test. Any idea, why this is happening to this bloc?
Expected: [ReadyToAuthenticateState:ReadyToAuthenticateState()]
Actual: []
Which: at location [0] is [] which shorter than expected
My bloc test
late MockUserAuthenticationUseCase mockUsecase;
late UserAuthenticationBloc authBloc;
setUp(() {
mockUsecase = MockUserAuthenticationUseCase();
authBloc = UserAuthenticationBloc(usecase: mockUsecase);
});
blocTest<UserAuthenticationBloc, UserAuthenticationState>(
'emits [MyState] when MyEvent is added.',
build: () => authBloc,
expect: () => <UserAuthenticationState>[ReadyToAuthenticateState()],
);
My bloc
class UserAuthenticationBloc
extends Bloc<UserAuthenticationEvent, UserAuthenticationState> {
final UserAuthenticationUseCase usecase;
UserAuthenticationBloc({required this.usecase})
: super(ReadyToAuthenticateState()) {
on<UserAuthenticationEvent>((event, emit) {
if (event is AuthenticateUserWithCredentialsEvent) {
_processReadyToAuthenticateEvent(event);
}
});
}
void _processReadyToAuthenticateEvent(
AuthenticateUserWithCredentialsEvent event) async {
await usecase(
UserAuthenticationUseCaseParams(event.username, event.password));
}
}
Update #1: I inserted initial state expectation also to the other working blocTest and got the same error. Seems we are not expected to test initial state.
This is the expect property documentation in bloc_test package:
/// [expect] is an optional `Function` that returns a `Matcher` which the `bloc`
/// under test is expected to emit after [act] is executed.
Meaning, inside the expect callback, you should put only the emitted states. Initial state is, well, the initial one, it is not emitted after you add an event to the BLoC.
If you want to verify an initial state of the BLoC, you can write a separate test for it:
test('should set initial state', () {
expect(authBloc.state, ReadyToAuthenticateState());
});
Related
I'm working on a flutter project and all of a sudden, the bloc builder I was using stopped recognizing a specific state change. Here is an example of the code
class ProfileBloc extends HydratedBloc<ProfileEvent, ProfileState>
...
on<ProfileSelected>(_mapProfileSelectedToState);
...
FutureOr<void> _mapProfileSelectedToState(
ProfileSelected event, Emitter<ProfileState> emit) async {
emit(state.copyWith(
profileSelectionStatus: ProfileSelectionStatus.selecting,
));
setHeaders();
setRedirect();
emit(state.copyWith(
selectedProfile: event.selectedProfile
profileSelectionStatus: ProfileSelectionStatus.selected,
));
}
...
BlocBuilder<ProfileBloc,ProfileState>(builder: (context, profileState) {
return profileState.profileSelectionStatus == ProfileSelectionStatus.selected ? Text("Profile Selected") : Text("Profile Not Selected");
}
...
when the BlocBuilder runs, it never registers the ProfileSelectionStatus.selected state. It always returns Profile Not Selected. That is until I refresh the browser, then it reports as Profile Selected. If I wrap the second emit(the profileSelected one) in the bloc in a Future.delayed(Duration.zero,()=>emit...), then the bloc builder works as expected returning Profile Selected after it's emitted.
The thing is, it worked for a long time, then without changing those two blocs of code, it stopped working. Any ideas on what's wrong?
So basically, I want to check whether I've passed whatever I need to pass to the HiveInterface and Box when I want to store something.
test.dart:
group('cacheStoraygeUser', () {
test(
'should call HiveInterface and Box to cache data',
() async {
when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockBox);
when(mockBox.put(0, tStoraygeUserModel))
.thenAnswer((_) async => tStoraygeUserModel);
// act
dataSourceImpl.cacheStoraygeUser(tStoraygeUserModel);
// assert
verify(mockHiveInterface.openBox(STORAYGE_USER_BOX));
verify(mockBox.put(STORAYGE_USER_ENTRY, tStoraygeUserModel));
},
);
});
My implementation for dataSourceImpl.cacheStoraygeUser():
#override
Future<void> cacheStoraygeUser(
StoraygeUserModel storaygeUserModelToCache) async {
/// Precaution to ensure that [STORAYGE_USER_BOX] has been opened.
///
/// If the box, is in fact not opened, Hive will just return the box since
/// the box is a Singleton. I think.
final box = await hiveInterface.openBox(STORAYGE_USER_BOX);
box.put(STORAYGE_USER_ENTRY, storaygeUserModelToCache);
}
When I try to run the test, it gives this error:
type 'Null' is not a subtype of type 'Future<void>'
MockBox.put
package:hive/…/box/box_base.dart:80
I already generated the mock classes for HiveInterface and Box. I think this is how I should do it if I want to test Hive, since I can't seem to generate Mock classes for Hive itself. But if you know a better or the correct solution then please tell me.
I also wrote another test for getting stuff from Hive. This works perfectly fine.
test(
'should return StoraygeUser from StoraygeUserBox when there is one in the cache',
() async {
// arrange
when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockBox);
when(mockBox.getAt(any)).thenAnswer((_) async => tStoraygeUserModel);
// act
final result = await dataSourceImpl.getCachedStoraygeUser();
// assert
verify(mockHiveInterface.openBox(any));
verify(mockBox.getAt(any));
expect(result, equals(tStoraygeUserModel));
},
);
Thanks in advance!
This problem has been fixed in Mockito 5.0.9
The problem stems from the fact that Box implements BoxBase rather than extends it.
Older versions of Mockito couldn't pick this up and thus, putAt and getAt and other methods are not generated in the mock classes.
I have this ViewModel and a Riverpod provider for it:
final signInViewModelProvider = Provider.autoDispose<SignInViewModel>((ref) {
final vm = SignInViewModel();
ref.onDispose(() {
vm.cleanUp();
});
return vm;
});
class SignInViewModel extends VpViewModelNew {
FormGroup get form => _form;
String get emailKey => _emailKey;
String get passwordKey => _passwordKey;
final String _emailKey = UserSignInFieldKeys.email;
final String _passwordKey = UserSignInFieldKeys.password;
final FormGroup _form = FormGroup({
UserSignInFieldKeys.email:
FormControl<String>(validators: [Validators.required]),
UserSignInFieldKeys.password:
FormControl<String>(validators: [Validators.required])
});
void cleanUp() {
print('cleaning up');
}
void onSubmitPressed(BuildContext context) {
// _saveRegistrationLocallyUseCase.invoke(
// form.control(_self.emailKey).value as String ?? '',
// form.control(_self.passwordKey).value as String ?? '');
}
}
abstract class VpViewModelNew {
VpViewModelNew() {
if (onCreate != null) {
onCreate();
print('creating');
}
}
void onCreate() {}
}
When I navigate to the page that has the signInViewModelProvider, it prints to the console:
flutter: signInPage building
flutter: creating
flutter: cleaning up
Then popping the page from the stack with Navigator.pop() prints nothing.
Then navigating to the page again prints the same 3 lines in the same order.
I expected onDispose to be called after Navigator.pop(), and not when navigating to the page that reads the provider. Why is onDispose being called directly after creation, and not when using Navigator.pop() (when I expected the provider to be disposed of since no other views reference it)?
Edit: I access the provider with final viewModel = context.read<SignInViewModel>(signInViewModelProvider);
I don't need to listen since I don't need to rebuild the page on
change. Is consumer less performant for this?
No, the performance is meaningless, even if it's listening it's not really affecting the performance because as a Provider there is no way to notify (which is not the case with a state notifier or change notifier)
Also if you don't care to listen after the value has been read The auto dispose understand no one is watching it and it disposes, it's better to use context.read when using tap or gestures that modify something
(I realize this is late to the party but maybe it'll help somebody)
The Riverpod docs come out pretty strongly against using read for the reason you said, i.e. performance/rebuilding concerns.
Basically you should always use watch except:
If you want your custom callback function called when it updates (use listen)
If the actual reading is happening asynchronously or in response to user action (like in an onPressed): this is the only time to use read.
If you're having issues with your widgets rebuilding too often, Riverpod has some ways to deal with that that don't involve using read.
I am using BLoC in flutter.
As soon as BLoC instance is created I want to make to API calls. To achieve that, I have added the following code inside the constructor.
class MyBloc extends Bloc<MyBlocEvent, MyBlocState> {
MyBloc() {
_repository = MyAccountRepository();
_myAccountList = List();
add(API1CallEevent());
add(API2CallEevent());
}
...
and the event handling part
...
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is API1CallEevent) {
var ap1 =
await _repository.getAPI1();
----
----
}else if (event is API2CallEevent) {
var api2 =
await _repository.getAPI2();
----
---
}
}
The problem I am facing is that the API calls are not executed parallel, which means after API1CallEvent is completed then API2CallEvent get executed...
is there any way to do that in parallel?
In my opinion, doing two API calls in parallel and expecting result at the same time is not much related to BLoC.
It is better if each bloc-event triggers a specific set of actions, and events are decoupled from each other.
Additionally;
Instead of raising an event inside init block, it is better to do that when you init Bloc inside a provider. See example;
BlocProvider<AuthBloc>(
lazy: false,
create: (context) => AuthBloc(
userRepository: _userRepository,
)..add(AppStartedEvent()),
),
This generates an event right after Bloc is initialized.
A bloc basically is a state machine. It does not do parallelism, that's not what it's built for. It's sequentially going from one state into another. In doing that, it can do things in parallel internally, but it cannot (or should not) take input in parallel.
If you want one event to execute multiple awaitable actions in parallel, you can do that:
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is CallTheAPIsEvent) {
final results = await Future.wait([
_repository.getAPI1(),
_repository.getAPI2()
]);
// do something with the results
yield ApisHaveBeenCalledState();
}
// more event handling
}
I'm using StreamControllers with Flutter. I have a model with some default values. From the widgets where I'm listening to the stream I want to supply some of those default values. I can see I can set an initial value on the StreamBuilder, but I want to use data from the model inside the bloc as initial data. So as soon as someone is using the snapshot data they get the default values. I've seen RxDart has a seed value, just wondering if this is possible without replacing with RxDart?
What you are looking for is StreamController#add method,
Sends a data event.
Listeners receive this event in a later microtask.
Note that a synchronous controller (created by passing true to the
sync parameter of the StreamController constructor) delivers events
immediately. Since this behavior violates the contract mentioned here,
synchronous controllers should only be used as described in the
documentation to ensure that the delivered events always appear as if
they were delivered in a separate microtask.
happy fluttering
The default value for a stream can be specified when the class is initialized after adding a listener for that stream.
import 'dart:async';
enum CounterEvent { increase }
class CounterBloc {
int value = 0;
final _stateCntrl = StreamController<int>();
final _eventCntrl = StreamController<CounterEvent>();
Stream<int> get state => _stateCntrl.stream;
Sink<CounterEvent> get event => _eventCntrl.sink;
CounterBloc() {
_eventCntrl.stream.listen((event) {
_handleEvent(event);
});
_stateCntrl.add(value); // <--- add default value
}
void dispose() {
_stateCntrl.close();
_eventCntrl.close();
}
_handleEvent(CounterEvent event) async {
if (event == CounterEvent.increase) {
value++;
}
_stateCntrl.add(value);
}
}