Flutter Testing — My Tips & Good practices
Hi! Today I’d like to share a few tips on how to write tests in Flutter. Some say that writing tests slows you down, but it is not true if you know how to write them efficiently. There are a few good practices on how to achieve that in basically every language and framework. In this article I will show you some of them in Flutter.
If you want to learn some basics about Flutter testing first, I encourage you to visit Flutter website. You can find there an explanation of unit, widget, and integrations tests. Otherwise, let’s start with today's tips!
Follow Given-When-Then
Are you familiar with Given-When-Then approach? It’s a style to formulate your tests. With that idea, you start a test by describing the initial state (e.g. Given user is logged in), then you describe what should happen (e.g. When user taps on a button), and finally you describe the expected state or result (e.g. Then user sees logged in message). This way lets you clearly separate the conditions, actions, and results and will help you see what’s the goal of each test.
Moreover, you can use that concept in the test name as well! This is how it may look like in Flutter:
testWidgets(
'GIVEN user is logged in '
'WHEN user taps on a button '
'THEN info is shown', (tester) async {
// given
when(user.isLoggedIn).thenReturn(true);
// when
await tester.pumpWidget(Home());
await tester.tap(find.byType(FlatButton));
await tester.pump();
// then
expect(find.text('I\'m logged in!'), findsOneWidget);
});
Note: If you don’t know what when(…).thenReturn(…)
means, you can follow reading to learn about mockito. It’s a must-have for testing!
Use Mockito
If you don’t know mockito yet, it’ll become your best friend in testing. Mockito is a library that allows to mock behaviors of classes. Why you may need that? The simplest use case is, of course, anything time and resources consuming, such as API calls, database operations, loading resources from network, etc. Those services may take a while and you can’t always predict their result, so you really don’t want them in your tests. Imagine running a test that randomly fails because you have a bad internet connection!
This is where mockito gets very useful. You can use it to mock behaviour of any(!) class you want and return the expected behaviour. For example:
when(apiClient.fetchKittyNames()).thenReturn(['Lilly', 'Bella','Coco']);when(databaseManager.saveProduct()).thenReturn(true);when(resourceProvider.getImage()).thenThrow(Exception());
This way you can check all possibilities in your tests with a full control over external dependencies. Pretty cool, right?
However, it’s not only for long-running operations. You can use Mockito for all your dependencies. Let’s look at the previous example with the user being logged in. If we want to test different behaviours based on the user state, we have to pass User
mock to Home
widget:
testWidgets(
'GIVEN user is logged in '
'WHEN user taps on a button '
'THEN info is shown', (tester) async {
// given
final user = UserMock();
... // when
await tester.pumpWidget(MaterialApp(home: Home(user: user)));
...
});
Where UserMock
is a new class created outside of the test with a mockito:
class UserMock extends Mock implements User {}
And this way we can control whether the user is logged in or not and check that in our test:
testWidgets(
'GIVEN user is logged in '
'WHEN user taps on a button '
'THEN info is shown', (tester) async {
// given
final user = UserMock();
when(user.isLoggedIn).thenReturn(true);
// when
await tester.pumpWidget(MaterialApp(home: Home(user: user)));
await tester.tap(find.byType(FlatButton));
await tester.pump();
// then
expect(find.text('I\'m logged in!'), findsOneWidget);
});
You can check the full source code on my GitHub:
That’s it for today! I hope this post clarified Flutter testing topic for you. If you still need more information, I encourage you to dive deeper into Flutter documentation where you can find a lot of useful articles and tutorials. Thanks for reading! 👋