Testing the theme switcher: refactor with confidence

Jorge Coca
Flutter Community
Published in
5 min readAug 31, 2018

In my previous article, that you can find here, we build a tiny application whose only purpose was to switch the theme by pressing two different buttons. However, the code was far from perfect, since we were injecting the same instance of our themeBloc to many widgets where the themeBloc had no business, just to be able to make our app work, breaking many good software design principles.

The problem with our code in an image: HomePage does not need the themeBloc instance at all, it is just a proxy, so at this moment our code is very couple and not easy to maintain.

As we can see in the graphic, our ThemeSwitcherApp and our ThemeSelectorPage need the same themeBlock instance, but our three hierarchy has the HomePage in between. How can we solve this problem?

InheritedWidget is what we are looking for

This is what we can find in the Flutter documentation about InheritedWidget :

Base class for widgets that efficiently propagate information down the tree.

What InheritedWidget will allow us to do is share the same instance of themeBloc without having to inject it in the HomePage , making our code cleaner, decoupled and more maintainable. Our tree would look something like this now:

With the ThemeSwitcher InheritedWidget, we can pass the themeBlock to lower leaves of our tree without modifying nodes in between.

… but I am not confident of making the changes without tests

Besides the problems with the code, what I had before was a functional app that works. Our users would be happy with the functionality, so I don’t want to proceed to make changes without verifying that everything is still working as expected. Therefore, we need tests!

Flutter provides three levels of testing:

  • Unit tests for testing particular classes or APIs.
  • Widget tests for testing Flutter widgets in isolation.
  • Integration tests for testing flows/paths of the app as a whole.

Since we already have a Flutter app that works, let’s start doing integration tests, so we can avoid manual regressions.

In order to run integration tests, we will need to create a new folder at the root of our project named test_driver. In there, we will include two files: app.dart and app_test.dart , that looks like this:

Since integration tests will run our app in an emulator/device, we will need a driver, in this case the FlutterDriver , to drive those interactions. Our test cases are defined inside app_test.dart , and what we do is to find the button that opens the ThemeSelectorPage , and make sure that when we tap on the buttons the theme is switched.

In order to run the tests, we will run this command on our terminal:

> flutter drive --target=test_driver/app.dart

Here you can see the results:

Another piece that is crucial to our application, and that we want to make sure we do not break, is our ThemeBloc . However, to test the ThemeBloc we will use unit tests instead, since what we want to verify it works is its API declaration.

To write unit tests, Flutter already provides a test folder; we just need to make sure that we import the test package, and we are set to go:

With these suite of unit tests, now I am confident the my ThemeBloc behaves as expected, so I could move pieces around with confident. These tests can be run from the IDE, and they will produce a nice green/red simple report in the IDE’s UI.

Lastly, the last piece that I want to test is my ThemeSelectorPage widget. Why? Well, I want to make sure that, no matter what, that widget contains my two buttons that toggle the theme between light and dark. In order to do this, I will run widget tests. These tests will be included also inside the test folder that Flutter gives you by default:

With all these tests, now I can introduce my InheritedWidget and refactor confidence.

Introducing the InheritedWidget

We are going to create a new file named theme_switcher.dart :

As we can see there, we passed the instance of ThemeBloc that we want to share in the with the lower layers of our widget tree, and we just need to override the updateShouldNotifiy(...) method whenever we change the instance of themeBloc .

With this, now we can modify main.dart to include our brand new ThemeSwitcher , like this:

No magic here; our ThemeSwitcher Inherited Widget integrates in our tree as any other widget. However, pay attention to one key detail: HomePage does not require an instance of themeBloc anymore! That’s how we decouple our pieces; now we can go to the HomePage and remove it:

Finally, our last step will be to consume our ThemeSwitcher inside our ThemeSelectorPage , since themeBloc does not need to be injected anymore. We will use the static function of that we created to help us use our inherited widget:

With all these changes applied, it is time to run our tests again to make sure everything is still working as expected, the way it worked before we make all the changes, and…

All the tests are passing!! We have modified the internals of our app to help us grow in the future, improving the quality, while having a way to verify that everything still works as expected. Even more, with our suite test, now we can keep adding new features, and we will have an automated way of running regression without any human interaction.

If you are interested in the entire code base, you can find all the changes here: https://github.com/jorgecoca/theme_switcher/tree/testing

I hope you liked it!

You can follow me on https://twitter.com/jcocaramos and see more code here on my public Github https://github.com/jorgecoca

--

--

Jorge Coca
Flutter Community

Android engineer at @bmwna. Born in Madrid, living in Chicago. I have watched La La Land more times than you… and I love singing and dancing in public xD