bloc_test does not catch states emitted by consecutive events added inside bloc. Same scenario passes in plain test but fails with blocTest.
I updated your flutter_login sample to replicate the issue.
Updated AuthenticationBloc to add LoggedOut event inside case of LoggedIn
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter_login/authentication/authentication.dart';
import 'package:meta/meta.dart';
import 'package:user_repository/user_repository.dart';
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository userRepository;
AuthenticationBloc({@required this.userRepository})
: assert(userRepository != null);
@override
AuthenticationState get initialState => AuthenticationUninitialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
final bool hasToken = await userRepository.hasToken();
if (hasToken) {
yield AuthenticationAuthenticated();
} else {
yield AuthenticationUnauthenticated();
}
}
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistToken(event.token);
yield AuthenticationAuthenticated();
add(LoggedOut());
}
if (event is LoggedOut) {
yield AuthenticationLoading();
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
}
}
In the first case, only first three states are caught by blocTest
Updated tests:
blocTest(
' Failing test: emits [uninitialized, loading, authenticated, loading, unauthenticated] when token is persisted',
build: () => authenticationBloc,
act: (bloc) => bloc.add(LoggedIn(
token: 'instance.token',
)),
expect: [
AuthenticationUninitialized(),
AuthenticationLoading(),
AuthenticationAuthenticated(),
AuthenticationLoading(),
AuthenticationUnauthenticated(),
]);
test(
'emits [uninitialized, loading, authenticated, loading, unauthenticated] when token is persisted',
() {
final expectedResponse = [
AuthenticationUninitialized(),
AuthenticationLoading(),
AuthenticationAuthenticated(),
AuthenticationLoading(),
AuthenticationUnauthenticated(),
];
expectLater(
authenticationBloc,
emitsInOrder(expectedResponse),
);
authenticationBloc.add(LoggedIn(
token: 'instance.token',
));
});
Hi @MohsinN 馃憢
Thanks for opening an issue!
I'll take a look shortly 馃憤
Hey sorry for the delayed response (just got back from vacation). I took a closer look and this is actually working as expected. blocTest uses emitsExactly under the hood which closes the bloc's sink and stream in order to provide instant feedback about the test (rather than a potential 30 second timeout). In this case, I would not recommend adding another event for the same bloc within mapEventToState as it's unnecessary and redundant.
You can instead refactor the code like:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter_login/authentication/authentication.dart';
import 'package:meta/meta.dart';
import 'package:user_repository/user_repository.dart';
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository userRepository;
AuthenticationBloc({@required this.userRepository})
: assert(userRepository != null);
@override
AuthenticationState get initialState => AuthenticationUninitialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
final bool hasToken = await userRepository.hasToken();
if (hasToken) {
yield AuthenticationAuthenticated();
} else {
yield AuthenticationUnauthenticated();
}
}
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistToken(event.token);
yield AuthenticationAuthenticated();
// do logout logic directly or extract into a private helper function
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
if (event is LoggedOut) {
yield AuthenticationLoading();
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
}
}
Hope that helps 馃憤
Hi,
Thank you for the detailed response.
Great work with library. Also, please let me know if I can contribute in any way. I can help out in writing tests if you like. Open to other suggestions as well.
Do let me know what you think.
No problem! If you find anything you feel is lacking please don't hesitate to open an issue and create a pull request 馃憤
Thanks so much! 馃檹
Awesome thanks! 馃槃
@felangel From your comment above:
I would not recommend adding another event for the same bloc within
mapEventToState
Is it a general rule? In other words, is it an anti-pattern to invoke add() in mapEventToState()? And, what if add() is invoked on another BLoC object instead?
Thanks in advance for your advice!
Most helpful comment
No problem! If you find anything you feel is lacking please don't hesitate to open an issue and create a pull request 馃憤
Thanks so much! 馃檹