Unit Testing with “Bloc”
In the last article, we created a simple login flow using the bloc and flutter_bloc packages. If you haven’t checked it out already, you can find it here.
When we left off, we had a working login flow but we were missing one crucial piece: unit tests.
The login flow consisted of two blocs (business logic components): AuthenticationBloc
and LoginBloc
. In this article, we’re going to write some unit tests to ensure that our business logic is robust and durable. Let’s get started!
We’re going to start off by writing the tests for our AuthenticationBloc
. First, let’s recap the role of the AuthenticationBloc
.
The AuthenticationBloc
converted AuthenticationEvents
into AuthenticationStates
. The AuthenticationEvents
our bloc responded to were:
AppStarted
added when the Flutter application first loads. It will notify bloc that it needs to determine whether or not there is an existing user.
LoggedIn
added on a successful login. It will notify the bloc that the user has successfully logged in.
LoggedOut
added on a successful logout. It will notify the bloc that the user has successfully logged out.
In response to those events, our AuthenticationBloc
emitted an AuthenticationState
which looked something like:
We have to extend Equatable
in order to allow us to check if two instances of AuthenticationState
are equal.
Now that we’ve recapped the function of the AuthenticationBloc
we’re ready to finally start writing some tests.
To get started, we simply need to import flutter_test
, and the mockito
package along with our authentication bloc. In our setUp
, we instantiate a new instance of AuthenticationBloc
to ensure that each test is run under the same conditions and does not influence subsequent tests. In addition, we are using mockito to create a mock instance of the UserRepository
which is a dependency of the AuthenticationBloc.
Our first test is just testing that our initialState is what we expect…nothing too fancy. Our second test is just a sanity check as well to make sure that when close is called the state of the bloc is not updated.
After running flutter test
our first two tests should be passing!
The remaining tests are what we really care about. If we take the AppStarted
group as an example, all we need to do is setup our expectation of what events the bloc will yield. In this case, when we add an AppStarted
event, we expect that the bloc will yield an AuthenticationUninitialized
state followed by an AuthenticationUnauthenticated
state because we have mocked our UserRepository
return false
for hasToken
.
The remaining groups have very similar flows and should all make sense now.
Now that we have the AuthenticationBloc
unit tested we can move on to the LoginBloc
.
Again, let’s recap what the role of the LoginBloc
was.
The LoginBloc
converted LoginEvents
to LoginStates
. We only responded to one LoginEvent
in our example: LoginButtonPressed
.
In response to LoginButtonPressed
, our bloc emitted a LoginState
which looked like:
Now we can jump into the tests.
Again, we have our initialState test and our dispose sanity check. Our main logic in the LoginBloc
was handling the LoginButtonPressed
event and returning a token on a success.
The test is very simple: we set up our expectation and then add our event. In this case, we expect that the LoginBloc
will yield LoginInitial
followed by a LoginLoading
and lastly a LoginInitial
in response to a LoginButtonPressed
event.
If we run our tests with flutter test
we should see that all of them are passing 🎉 🎉 🎉.
We are now able to write unit tests to make sure our blocs are behaving as expected in all scenarios. You can find the full source here and the repository here.
As always, if you enjoyed this exercise as much as I did you can support me by ⭐️the repository, or 👏 for this story.