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. 😄
- Creating the test directory
- Unit testing the
- Integration testing the
- Running the test cases 🏃
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
Architect your Flutter project using BLOC pattern
Hi Folks! I am back with another brand new article on Flutter. This time I will be talking and demonstrating to you…
Architect your Flutter project using BLOC pattern (Part 2)
Hi Folks! This article is a continuation of my previous article “Architect your Flutter project” . As promised in my…
Compile time Dependency Injection in Flutter
Hey Folks! I am back with another brand new article on Flutter. This time I will be covering a very interesting and…
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:
Before moving to the testing part let’s refactor the approach we followed in the previous article to add inject.dart in the project.
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
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
In the above code, you will be unit testing the
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
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
BLoC. 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
error . Now you also need to refactor these classes i.e
repository.dart file and paste below code in it:
You just added the generic
State as the return type. It will hold either a
movie_bloc.dart file and paste below code in it:
fetchAllMovies() you are checking the
state and passing the correct data to the stream.
movie_detail_bloc.dart file and paste below code in it:
Same stuff inside
fetchTrailersById() . 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
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 the
inject.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
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.
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.
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:
- You need to have a
main()method where you will put all your test cases.
group()is a method which will hold multiple test cases.
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.
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.
- You are passing different JSON body and status code to
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:
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:
Yay!! 👏 👏 You have successfully run all your unit test cases. You have tested both
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 the
integration_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 the
main() 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.