[Flutter/Dart] Bloc Pattern with Freezed Part-2 Testing
Hello! I hope you’re doing well. Today, we will test our previously created MusicPlayerBloc to ensure it functions as intended.
Testing your code is always a good practice, as it allows you to write modular and functional code, and understand the logic better. As you develop the bloc, it’s essential to think about all the possible test cases. This will help you check that all cases still apply every time you make changes to the code. The purpose of writing tests is to automate the process and ensure that the functionality remains the same every time you add or change something in the code. By writing possible scenarios and use cases, you can be confident that the code is working as expected.
Introduction
There are several types of tests, including unit testing, widget testing, integration testing, golden testing, and user acceptance testing.
Unit tests help us verify the smallest pieces of code, known as units, such as a function for image-to-byte conversion, a network call, JSON parsing, or in our case, a bloc.
Widget tests ensure that widgets in Flutter display the correct information. They often include the units tested in unit tests. For example, in a counter app, you can verify that after tapping the “increase” button, the widget displaying the current count will show the increased value. Or in a list of items displayed using a FutureBuilder, you can verify that the items are properly retrieved and displayed from a network call.
Integration tests involve simulating user interactions and feature flow in the app to verify that all functionalities work together and display the correct information on the screen. For example, a login test can be an integration test where you programmatically input an email and password, and verify if the login is successful or not.
Unit, widget, and integration tests can be compared to atoms, molecules, and organisms. First, you have unit tests, then use the units to test widgets, and finally write integration tests to see everything in action.
Golden tests are a form of automated testing that compare the current state of screens with pre-approved images to verify that everything is functioning as expected with each change in the code. If the output differs from the golden image, the test will fail, and the developer will be notified of the difference.
User acceptance testing involves real users testing the functionality and user experience. This test is crucial as it provides real-life feedback on how users will interact with the app. Even with multiple tests, it is impossible to simulate all real-life scenarios, so user acceptance testing is essential to ensure the app is functioning as expected.
A use case for user acceptance testing (UAT) could be testing the functionality and user experience of a new e-commerce platform. During UAT, a group of real end-users would be invited to navigate the platform, purchase items, and provide feedback on the overall experience. The goal of UAT is to identify any issues with the platform that real users may encounter, such as difficulty in navigating the site, issues with the checkout process, or problems with the mobile app. This feedback can then be used to make improvements to the platform before its official launch.
Testing MusicPlayerBloc
To ensure that our code functions as expected, we first create mocks of the TrackTime and PlaylistRepository functionalities. We use the mockito
library to create these mocks and give them optional names using the as
keyword. Then, we import the not-yet-generated file music_player_bloc_test.mocks.dart
. Finally, we run the following command to generate the mocks.
flutter pub run build_runner build --delete-conflicting-outputs
To set up the bloc, we use MockTrackTimer
and MockPlaylistRepository
. The mock repository allows us to simulate the behavior of different functions and return any desired output. In our scenario, we return the constant Playlist data blues90s
. Additionally, we establish a base state for the bloc. This setup provides a controlled environment for testing the bloc’s behavior and ensuring that it functions as intended.
Our first test will be about playing the first track. First, we give the bloc we’re working on by passing it to build
. Then we set up the trackTimer
. Regardless of the input trackDuration
, passedTime()
will always return 0 Duration. The act section will include events. We only added a play event. We can skip states. If there are many state changes after an event is dispatched, you might focus on the latest two by skipping others. By skipping one, we skip the initial status of the state. And after the event occurs, we expect the first loading status, then the playing status.
I’m not calling playing and loading states because they’re actually in the same state, but they’re different statuses.
In this test, we set up the passedTime
to return 0 Duration and seed a state. This is done because a starting point is needed to simulate a scenario. In this case, the starting point is the seed state with the current status as playing. By only updating the status, the current track being played is the first track of blues90
. The next event is then dispatched, which simulates a network call. We also wait for 200ms. If the bloc is working properly, we should first see the loading status, followed by the next track’s index and the playing status.
In this test, we set the starting state of the bloc to playing and the currentTrackIndex
as the last item of the track list. We then dispatch the next
event. Since we are already on the last track, the event should stop at the last track and not move on to the next one. The bloc should correctly reflect this state change.
In this test scenario, we start with a playing status, with the current track being the 6th one and the current time being 10 seconds. Then, the previous
event is dispatched, and we wait for 200ms. The expected outcome is that the next status should be loading along with the current time and current track index. After the loading, the bloc should start playing the previous track, which means the next status should be playing, but with the current track index being 4 and the current time being 0.
You can check out all tests below. The same logic applies to other tests.
Conclusion
Thank you for reading up to this point. I hope you learned something new today. In part 3, we will finally create UI and integrate it with the bloc.
Source code
Don’t forget to give claps and share if you found the article helpful. You can also check out my website and follow me on my socials.