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 dispatched when the Flutter application first loads. It will notify bloc that it needs to determine whether or not there is an existing user.

LoggedIn dispatched on a successful login. It will notify the bloc that the user has successfully logged in.

LoggedOut dispatched 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 dispose 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 dispatch 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 dispatch our event. In this case, we expect that the LoginBloc will yield LoginInitial followed by a LoginLogin 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.