Flutter Golden Testing with Bloc
Using more advanced state management such as bloc doesn't mean you need to give up on golden tests. In this article, you'll quickly learn how to test your widgets with bloc. So let's jump right into it.
If you're not familiar with golden tests, I have written another article on introduction to golden tests.
I prepared a simple bloc example that presents the latest F1 GP Podium places (because I love F1). We won't focus on the Cubit or widgets implementations as it's irrelevant for the tests, but you can download the complete example on GitHub and check it out yourself. The important part is that we inject our Cubit like this:
And further, in the widget tree, a simple
BlocBuilder is specified with three states (loaded, loading and failure):
We want to test every part of the UI, Loading, Failure, and Loaded states. To achieve this, we need to mock our Cubit first, but let's start with a simple golden test for our widget, without worrying about that part yet.
First, let's create our mocked Cubit. The most straightforward solution is to use
bloc_test. Different from mocktail or mockito,
MockCubit reduces boilerplate we have to write. So let's use it:
It also provides a handy method to mock the Cubit's state, so we don't need to do it by hand with mocktail:
Ok, but we still need to provide it to our widget, but how? Well, there are a few approaches, as always. What I like the most is just adding a nullable cubit field to our widget like this:
@visibleInTesting flag will show a warning when we try to pass Cubit outside of our tests.
Also, don't forget to mock the
load method used when creating the Cubit - that goes for every function that your widgets use. Our final test code should look like this:
Ok, before we jump to other states, we need to think about our approach here.We can either create separate golden tests for each or combine everything with scenarios for each.
Which approach is better? Using three different files can make the tests more clear and increase readability. It's also better visualized when one fails - we immediately know where to look for the issue.
On the other hand, using one file for everything can save time when running the tests. You might've noticed that the golden tests aren't the fastest. Part of it is because it loads the files to the memory. If we can decrease the number of loading we do, the tests should run faster. It might not be a big difference on the span of one test, but on the span of multiple tests, this changes.
I always used the multiple files approach, but I'm willing to give a try to the one file per page test as I'm curious about how much time it'd save. I'll also present this approach as it's a bit trickier.
Widgets are not built at the exact time when they're added to the device builder. They're rendered when they're pumped to the widget tester. Because of that, we need a better solution for our mocks, the one that'd mock the data just before a particular widget is built. Fortunately, there's a simple solution. We can use the Builder widget and wrap our widget like this:
Now let's finish up with the rest of the states:
Network calls are unavailable while testing. That's why the only solution is to mock something. Well, as it's probably possible to mock network responses, that'd require much work, and is that necessary? There's a more straightforward solution - create a wrapper widget for our images and display different results depending on if the app is running in tests or not.
PS: You can use some asset image that better represents what you're using, but remember that it's probably not worth adding it to the app just for the tests as it increases the app size.
Golden tests with native views are also not possible, as they need native development to run correctly. However, don't give up on golden tests when your app uses google maps, pdf viewers, or other native views! You can create a similar wrapper, as in the example above, to mock a different widget just for the tests.
Progress indicators are one of those animation things you might have problem testing. Again the simplest solution is to mock it for tests. It might be helpful when you have only part of the UI animating, and the other parts of the UI aren't - to make sure it's correctly aligned - make sure to use the exact sizes of the widget you're mocking.
Running golden tests on different environments is usually not a good idea. Each system has some minor differences in the way the widgets are rendered. You can replace your
flutter_test_config.dart content with this method to prevent running on different environments:
Overriding default devices for all tests
GoldenToolkitConfiguration allows overwriting default devices with the
defaultDevices flag for golden tests.
Golden tests can boost your development productivity, so I hope it gives you a better idea of using them within your bloc widgets.