Testing BLoC and Repository in Flutter

Integration and Unit testing in Flutter

Make your app’s features bullet-proof by testing them

Hi Folks! This is the last article for my series “Architect your Flutter project using BLoC pattern”. In this article, you will be learning another most important and critical part of software development i.e “Testing”. You will learn both Integration and Unit testing. This will help you deliver software that meets expectations and prevents unexpected results. So without further delay let’s write some test cases. 😄

Content

  1. Creating the test directory
  2. Unit testing the Network Layer💻
  3. Integration testing the BLoC
  4. Running the test cases 🏃

Prerequisite

As this article is part of a series. I expect you to go through the previous parts of the series(links below). This will help you understand the whole architecture of MyMovies app and what were changes made in different parts of the series.

Other parts of the series

Part 1

Part 2

Part 3

Goal

Have you ever launched an app without testing? I hope your answer is “NO”. You don’t want your customers to have unexpected crashes or a buggy app. There is a beautiful quote regarding testing “If you don’t like testing your product, most likely your customers won’t like to test it either”.

Software testing is the most crucial part of any software development lifecycle. This will help you find bugs and errors that were made during the development phase. Ultimately this methodology will help you ship quality products to your customers. Today you will be testing the features of MyMovies app which was built in the previous parts of the series.

Unit and Integration test

A unit test is a test written by the programmer to verify that a relatively small piece of code is doing what it is intended to do. They are narrow in scope, they should be easy to write and execute.

Part of being a unit test is the implication that things outside the code under test are mocked or stubbed out. Unit tests shouldn’t have dependencies on outside systems.

An integration test is done to demonstrate that different pieces of the system work together. Integration tests can cover whole applications, and they require much more effort to put together. They usually require resources like database instances and hardware to be allocated for them. The integration tests do a more convincing job of demonstrating the system works (especially to non-programmers) than a set of unit tests can, at least to the extent the integration test environment resembles production.

Note: source StackOverflow

The above explanation is more than enough to understand the difference between unit and integration tests. This will help you understand which part of the code can be unit tested or you need to write integration tests.

Creating the “test” directory

Download the project on your machine from here and open it in your favorite IDE. I will be using Android Studio for the demo. Make sure you download the 2nd branch as it has the Dependency Injection module which will make testing much easier.

Once you open the project in your IDE. The project structure should look as below:

Refactoring dependencies

Before moving to the testing part let’s refactor the approach we followed in the previous article to add inject.dart in the project.

Delete the injection sub-module from your project. Open your pubspec.yaml file and delete the following dependencies:

Now update your pubspec.yaml file by adding the following dependencies:

In the above list of dependencies, you have replaced the inject.dart framework’s path with url . Now instead of maintaining a local sub-module of the inject.dart framework you will add it to your project during compile time.

You have also added mockito which will help you mock some objects while testing. Now run flutter pub get and everything should work fine.

Creating the test module

Now create a test directory in your project. This will hold all your test cases. This is how the project structure should look like:

Layers to test

Now let’s discuss what are the layers you are going to test. Below diagram should make things clear:

In the above diagram, you are going to unit test the network layer(MovieApiProvider). You will check if the logic of parsing the JSON is correct or not and also test how your code behaves when different HTTP status codes are provided.

Next, you will write some integration tests to see if the BLoC, Repository and Network layer modules are integrated logically or not.

Now create two sub-directories inside the test directory. Name them as unit_test and integration_test . These directories will hold a specific type of test cases i.e unit and integration. Now your project structure should look like this:

Unit testing the Network Layer

It’s time to write some unit test cases for the Network layer i.e MovieApiProvider . Let’s have a look at the current code of the MovieApiProvider :

In the above code, you will be unit testing the fetchMovieList() and fetchTrailer() methods. But you need to refactor the above code so that instead of throwing Exceptions you will provide a particular state . So let’s add the necessary code for this.

Create a file state.dart inside the models directory and paste below code in it:

Whenever you make a network call, you will either get a success or error. So keeping that in mind you will use this class to provide a particular state from your network layer to the upper layers i.e Repository andBLoC. This approach will also help you write test cases which will expect a particular state.

Let’s refactor the MovieApiProvider class as below:

Now instead of directly throwing Exceptions you are providing a particular state either success or error . Now you also need to refactor these classes i.e repository.dart, movies_bloc.dart and movie_detail_bloc.dart .

Open therepository.dart file and paste below code in it:

You just added the generic State as the return type. It will hold either asuccess or error state.

Open themovie_bloc.dart file and paste below code in it:

Inside fetchAllMovies() you are checking the state and passing the correct data to the stream.

Open the movie_detail_bloc.dart file and paste below code in it:

Same stuff insidefetchTrailersById() . Checking the state and passing the appropriate data to the stream.

The last file you will refactor is the movie_detail.dart. Copy and paste below code in it:

Previously you were using FutureBuilder to load the trailer from the network. But now you don’t have to do it anymore as you have fetched the trailer JSON data from the network inside the MovieDetailBloc class, converted it to TrailerModel object and added it to the stream. So now you are directly observing the stream which consists of an TrailerModel object.

Writing unit test cases

Phew! Lot’s of refactoring 😅 but this will ultimately help you test your code with more clarity. Now without any further delay let’s focus on writing some unit test cases.

Since you are using theinject.dart framework to provide the dependencies to the required classes. You have to create a separate dependency graph for unit tests because they shouldn’t have dependencies on outside systems.

Create the below files inside your unit_test directory:

Open mock_client.dart file and paste below code in it:

You have created a MockClient which will provide you with different Http response in each test. This will not make a real network request. 😃 The purpose of a unit test is to test the logic of the code and not the final functional requirement.

Now open provider_injector.dart file and paste below code in it:

You will see an error at line number 2. This file will be generated by build_runner during compile time.

Open provider_module.dart file and paste below code in it:

Here if you take a closer look at line number 12. You are injecting a MockClient object instead of http.Client object. We are just providing mock objects because the purpose of a unit test here is just to test the piece of code which will handle the network response.

Now is the time where you will write your first unit test case. Open the provider_test.dart file and paste below code in it:

Let’s go through the above code step by step:

  1. You need to have a main() method where you will put all your test cases.
  2. group() is a method which will hold multiple test cases.
  3. test() is a method where you will write your single test case. If you want to create multiple test cases you have to declare multiple test() methods and write individual test cases in it.
  4. expect() will take two parameters and check if both are same or not. This is where your test case will either pass or fail. In the above code, the first parameter in the expect() is the actual type and the second is the matcher type.
  5. You are passing different JSON body and status code to fetchMovieList() and fetchTrailer() to check if these methods return the expected state.

Running your first unit test

Open your terminal and navigate to the root of your project. Run the below command:

flutter packages pub run build_runner build --delete-conflicting-outputs

If you got the Succeeded message then everything is working fine 👏. Now a file named provider_injector_inject.dart is added in your unit_test directory. If you open the file you will see all the required objects are created and are ready to be injected. Now the error in your provider_test.dart file should have gone.

Go back to provider_test.dart file and click on the green button beside the main() method. You will see the following output:

fail

The test cases are failing because you forgot to add the API key in your MovieApiProvider class. If you have already added the key then you will see the following output:

success

Yay!! 👏 👏 You have successfully run all your unit test cases. You have tested both fetchMovieList() and fetchTrailer() method’s logic with different scenarios.

Integration testing the BLoC

Now let’s write some integration test cases and see how the individual modules integrate together.

Create a file bloc_test.dart under theintegration_test directory and paste below code in it:

You must be wondering why we didn’t create a separate dependency graph for this. The answer is very simple, you don’t have to provide any mock objects. We will be using the project’s DI module to provide the dependencies because you are actually making a network call and checking the complete integration between network layer, repository and bloc modules.

Running the test cases

Now press the green button beside themain() method and you should see the following output:

This success result shows that the network, repository and bloc modules are integrated correctly and are behaving as expected.

We have reached the end of this article. I hope you have learnt how to unit test your code and write some integration tests. Next time whenever you make a change to your codebase, just run these test cases to make sure that everything is working fine. If you facing any issues put a comment or connect with me at LinkedIn or follow me on Twitter.

📝 Read this story later in Journal.

👩‍💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Sagar Suri

Sagar Suri

Google certified Android app developer | Flutter Developer | Computer Vision Engineer | Gamer