Hi @felangel , I've been following your tutorial on unit testing bloc for doing unit tests in my authenticationBloc.
But my tests are getting failed.
LoginButtonPressed emit token on success [E]
Expected: should do the following in order:
* emit an event that AuthenticationInitial:<AuthenticationInitial>
* emit an event that AuthenticationLoading:<AuthenticationLoading>
* emit an event that AuthenticationError:<AuthenticationError>
Actual: <Instance of 'AuthenticationBloc'>
Which: emitted * AuthenticationInitial
* AuthenticationLoading
* AuthenticationError
which didn't emit an event that AuthenticationError:<AuthenticationError>
package:test_api expectLater
package:flutter_test/src/widget_tester.dart 271:10 expectLater
test\authentication_bloc_test.dart 66:7 main.<fn>.<fn>
AuthenticationInitial - Initial state
AuthenticationLoading - Loading the UI after the user taps on Login button.
AuthenticationAuthenticated - After fetching response from the server and validating it (by checking the status code), this will be fired.
Here's my authentication_bloc_test.dart :
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:platinum_card/src/repositories/user_repository.dart';
import 'package:platinum_card/src/blocs/authentication/authentication_bloc.dart';
import 'package:platinum_card/src/models/login/login_response.dart';
import 'package:platinum_card/src/models/error_response.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
AuthenticationBloc authenticationBloc;
MockUserRepository userRepository;
setUp(() {
userRepository = MockUserRepository();
authenticationBloc = AuthenticationBloc();
});
tearDown(() {
authenticationBloc?.close();
});
test('initial state is correct', () {
expect(authenticationBloc.initialState, AuthenticationInitial());
});
group('AppStarted', () {
test('emits [AuthenticationUnauthenticated] when app started', (){
final expectedResponse = [
AuthenticationInitial(),
AuthenticationUnauthenticated()
];
expectLater(
authenticationBloc,
emitsInOrder(expectedResponse),
);
authenticationBloc.add(AppStarted());
});
});
group('LoginButtonPressed', () {
test('emit token on success', (){
final expectedResponse = [
AuthenticationInitial(),
AuthenticationLoading(),
AuthenticationAuthenticated()
];
LoginResponse loginResponse = LoginResponse(status: 'SUCCESS', statuscode: 200, payload: Payload(token: 'valid-token', email: 'valid-email', roles: []));
ErrorResponse errorResponse = ErrorResponse(status: 'FAILURE', statuscode: 404, error: Error(code: 'NOT_FOUND', message: ''));
when(userRepository.authenticate(username: 'valid.username', password: 'valid.password'))
.thenAnswer((_) => Future.value(loginResponse));
expectLater(authenticationBloc, emitsInOrder(expectedResponse));
authenticationBloc.add(LoginButtonPressed(username: 'valid.username', password: 'valid.password'));
});
});
}
Here's the authenticate method in userRepository :
Future<Object> authenticate({
@required String username,
@required String password,
}) async {
Map<String, String> body = {"email" : username, "password" : password};
Map<String, String> headers = {"Content-type": "application/json"};
final response = await client.post("$baseUrl/user/login", headers: headers, body: jsonEncode(body));
if(response.statusCode == 200) {
LoginResponse loginResponse = LoginResponse.fromJson(json.decode(response.body));
persistToken(loginResponse.payload.token);
return loginResponse;
} else {
print('${response.statusCode} and body = ${response.body}');
ErrorResponse errorResponse = ErrorResponse.fromJson(json.decode(response.body));
return errorResponse;
}
}
Please help me with the unit tests for the same.
Hi @rakeshh025 馃憢
Thanks for opening an issue!
Unfortunately, that tutorial is a bit outdated. I would highly recommend using the bloc_test package to unit test blocs. You can find a more up-to-date tutorial here.
Can you please share a link to the repo so I can run/debug the tests myself? I would be happy to open a pull request with any fixes 馃憤
Hey @felangel , thanks for your response. You may not need a repo. It's pretty much the same as your login example using Bloc. The only change is, you simply return a static 'token' string when logging in , instead I'm invoking a real API. That's it
No problem! It would still make it much easier to help if you share a link so I can reproduce the problem locally, thanks!
Closing for now since I am unable to reproduce this issue and there isn鈥檛 a sample app provided. Feel free to include a link to a sample app which reproduces the issue and I鈥檓 happy to take a look 馃憤
@felangel How to write unit test cases for States that doesn't extend Equatable?
@rakeshh025 you have to write you own Custom Matchers.
For simple cases you can use built-in matchers like:
expect: [isA<StateA>(), isA<StateB>()]
@felangel Any link to a detailed tutorial or example?
@rakeshh025 unfortunately I don't have a bloc tutorial on testing without Equatable. Is there a reason why you don't want to use Equatable or just override == and hashCode some other way? I believe in most cases you want states to be "data classes".
@felangel The reason I don't use Equatable is I want to catch duplicate states. Here's my use case :
I have a list of cards in the screen. Each card has a check/uncheck image at the bottom of it, i.e. user can tap on a card to check it or to uncheck it. When the user taps on a card, event is being passed to the bloc along with the tapped 'Item'. Bloc checks if it's already checked; if so, it unchecks that item and fire the whole "data classes". Using equatable, blocBuilder doesn't catch states.
I'm writing unit test cases for the same. Please help me how to do it?
@rakeshh025 it sounds like a bug in the implementation. You should be able to accomplish that use case with Equatable. Are you including the check/uncheck in the state's props?
@felangel Nope. Bloc has the collection of 'Item's. When user taps on an Item, I pass the tapped item to the bloc -> bloc updates it in the collection and sends the updated collection in the state.
Does your Item class extend Equatable? It would be great if you could share a link to the code and I can take a look.
@felangel Sharing it.
@felangel invited you to the repo. Please help me!
@felangel any update?
@rakeshh025 created a pull request with a working blocTest and some other suggestions.
@felangel Thanks for your help. I ran your code and found out that when I tap on an item, the state 'ShowItems' never get fired (the same reason I didn't go for equatable). Can you please take a look?
@rakeshh025 no problem! Just updated the pull request to fix the functionality. The problem was your Item was not extending Equatable and your bloc was mutating state rather than creating new state instances. Hope that helps!
@felangel A couple of quick questions :
Itemextend Equatable?"bloc was mutating state rather than creating new state instances" Can you please explain it more?ShowItems) is same as current one (ShowItems)? @felangel , Can you please let me know how to pass the same bloc to a different page?
For example :
With reference to my application, lets say on tapping an item, user navigates to it's detail page. When user does some action there in the detail page, I need to fire an event and pop that page and come back to my initial page and the initial page should react to the event fired at the detail page.
How to achieve this? Please help.
@rakeshh025 please, before asking a question, read the docs. They are there for a reason. https://bloclibrary.dev/#/recipesflutterblocaccess
@rakeshh025
Item needs to be Equatable because it is part of your state and you want to have value equality rather than reference equality.
Most helpful comment
@rakeshh025 you have to write you own Custom Matchers.
For simple cases you can use built-in matchers like: