Flutter Bloc Unit Testing
Hi Guys. Hope You know about the flutter bloc state management package. There are many state management ways like provider, redux, GetX..etc. I would like to explain more deeply in Flutter bloc unit testing with the bloc test package. You can refer It’s documentation too. So Let’s start!
Let’s go through the concepts using the following example test project. First of all, we need to add the bloc test main flutter package into the dev dependencies in our project. Also, Refer the link below.
bloc_test: latest version number
Huge thanks to this package creator, We can test any bloc in flutter very easily. So for the network calls(API), many people use HTTP/dio packages. So we need to mock those network calls in unit testing for some scenarios. For that, we use the mokito package for mocking network calls. I have used dio instead of http package to call the API. So for that mocking part, I needed http_mock_adapter . Finally, my dev dependencies look like this,
dev_dependencies:flutter_test:sdk: flutter#bloc unit testbloc_test: ^9.0.3#mockmockito: ^5.1.0http_mock_adapter: ^0.3.2# Model generation and serializationbuild_runner: ^2.1.10built_value_generator: ^8.1.4
Additionally, I used built value and Its generators to generate model classes for serialization. I used a free API called fake store(https://fakestoreapi.com) to explain the real data-getting process. It is free.
I created only one page here because I need to explain more about the bloc unit testing rather than app development using flutter in this short article. For that purpose, I have created a home page and created a home page bloc and its logic with API calls. So Let’s start on my main. dart file.
BlocProvider(
create: (context) =>
HomeScreenBloc(homeRepository: getIt<HomeRepository>()),
child: const HomeSceen(), ),
Here I created a home page bloc inside my app using this bloc provider. Added home repository object as the singleton object. The flutter get it package gives us dependency injection. So I have used here that singleton object which is created in the app initialization in the main function of the app. It reduces unnecessary object creation in the runtime of the app. Bloc observer has been added because we can track state changes in debugging. Logs are added according to each state change of the homepage bloc.
My homepage bloc has 4 main states and one event which needs to get data from the API. Main states are HomeScrenLoading, HomeScreenInitial, HomeScreenFailed, HomeScreenProductsLoaded. The main purpose of this bloc is to get data from the API for the home screen. So my homepage is as follows.
The home repository is essential here because we need that to call the API and get a response. I need to get data as soon as the bloc is created.
add(GetHomeProducts());
This event is added inside the constructor. When the provider creates the bloc this event is added to the bloc.
So My homepage is simple. It is a list view that includes data that gets from the homepage bloc. Bloc provides a product list and I have added listview for that product list data.
Bloc builder gets the latest state from the homepage bloc and rebuild the homepage according to the state.
Unit Testing part for the bloc
I had to consider two parts of this bloc.
1)Success Scenarios- This includes all success scenarios for the homepage bloc. All API calls have status code 200. Assumed There are no server/internet/other errors.
2)Error Scenarios- This includes all error scenarios for the data getting processed on the homepage. Status code will not equal 200 and server/internet errors are considered in this scenario.
- Success Scenarios
Used group here because we can separate the above two scenarios.
List<dynamic> data = [ { "id": 1, "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops", "price": 109.95, "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday", "category": "men's clothing", "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg", },];
This data is static dummy data that needs for the testing process. Inside this list includes product models.
setUp(() {dio = Dio();dioAdapter = DioAdapter(dio: dio);dio.httpClientAdapter = dioAdapter;});
This setup function is called before each test is run. Also, this is applied inside the group only. For testing, we need to mock data. For that, I have added HTTP client adapter and set it to the dio object. Now the fun part is using the bloc test package. It is a very smart package. First I need to test when the response data list is empty what happens to the bloc state. It should be HomeScreenProductsLoaded with no data(empty product list).
Setup like the normal setup that I mentioned above. I have used dioAdapter to get a mock response with empty data. But the status code of the response is 200. So any http gets a request to this URL, respond with status code 200 and empty list([]). Build function for the bloc creation. If you want to delay time you can use wait duration as I have used here. “expect” is the state list function which includes the expected state list for this bloc. I didn’t need to call the event because it has already been included in the bloc constructor. So when the bloc is created it will be called the data getting event for the bloc.
If the bloc is worked without any failure It should be returned. HomeScrenLoading
HomeScreenProductsLoaded with an empty product list sequentially for the above test case. Let’s move on to the next test for the success scenario.
When data is available API will return the success response with data. So in that case bloc should be worked as above. To test that case I mocked the response with dummy data and check the last state has that exact data model. States were the same as in the previous test case but the data is different.
Now my main success scenarios were over.Let’s move into error scenarios.
2. Error Scenarios
In error scenarios, we can check any errors which occur during the bloc state changes. For example, I have added a response null scenario for the above test case. You can add more error checks as you wish. In my example, HomeScreenFailed state is emitted as the last state for each and every errors in the bloc.
This test case has little different than the previous test cases. This will return null as the response for that get request which is triggered in the home page bloc. So when the event is triggered loading-> failed states are emitted sequentially. The last state is HomeScreenFailed. If the bloc is working correctly these states should be emitted. I am checking that in this test case.
So At last I need you to mention some important points that I have explained in this article.
1.You can divide all your bloc behaviour into success and fail states.
2.Bloc test package can be used to test blocs very effectively.
3.There are many ways to create more test cases in flutter unit testing.
4.My main purpose is to test bloc flow and state emitting in the correct manner.
Hope you get the idea about bloc unit testing. You can read more in the bloc_test documentation. Full source is available here https://github.com/Dineth95/Bloc-Unit-Test
Thanks for reading this article!. See you soon with new one. Please share and clap too!!