How to do test your Flutter app

Trong Dinh Thai Hoang
5 min readSep 19, 2019

This is the next part of the previous article I wrote before. If you haven’t read it yet, I highly recommend you should read it first. ’Cause I will do a demo based on it.

1. Requirements:

  • This tutorial requires you need to know BloC knowledge. If not, please read this document first.

2. How many kinds of test:

There are 3 types of test:

  • Unit test: tests a single function, method, or class.
  • Widget test: (in other UI frameworks referred to as component test) tests a single widget.
  • Integration test: tests a complete app or a large part of an app.

a well-tested app has many unit and widget tests, tracked by code coverage, plus enough integration tests to cover all the important use cases.

https://flutter.dev/docs/testing

In this article, I only focus on unit test and widget test. The integration test should be another thread.

3. App structure:

App structure

4. Unit test:

Understand the unit test method:

Test scenario:

  • Which class you want to test: utils.dart
  • What method you want to test: isValidEmail
  • What input: trongdth@gmail.com
  • What output you expected: true

Full source code:

import 'package:flutter_bloc_back4app/helpers/utils.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test('test email valid returns true', () {
/*
input: trongdth@gmail.com
expected: true
*/
var email = 'trongdth@gmail.com';
var actual = Utils.isValidEmail(email);
var expected = true;
expect(actual, expected);
});
}

5. Widget test:

Understand the widget test method:

Understand widget_test.dart

  • When you create a new futter project. There is a sample code which has widget_test.dart under test folder. The code looks like this:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tokoin_wallet/main.dart';

void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());

// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);

// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();

// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
  • Let's go through the code to understand what it does:
await tester.pumpWidget(MyApp()); // Load MyApp widget for testingexpect(find.text('0'), findsOneWidget); // use global object 'find' to find the widget has text '0'. The expectation is 'findsOneWidget'expect(find.text('1'), findsNothing); // use global object 'find' to find the widget has text '1'. The expectation is 'findsNothing'await tester.tap(find.byIcon(Icons.add)); // try to send the 'tap' action to the Icon widget which has 'Icons.add'await tester.pump(); // call 'build' function of the MyApp widgetexpect(find.text('0'), findsNothing); // use global object 'find' to find the widget has text '0'. The expectation is 'findsNothing'expect(find.text('1'), findsOneWidget); // use global object 'find' to find the widget has text '1'. The expectation is 'findsOneWidget'

Test scenario:

  • Which widget you want to test: login.dart
  • Your scenario: based on app structure, if user logins unsuccessfully, you are still in the login page instead of the home page.

Make your test:

  • Create an abstract BaseUserRepository class:
abstract class BaseUserRepository {
Future<ParseUser> authenticate({@required String username, @required String email, @required String password,});
Future<ParseUser> signup({@required String username, @required String email, @required String password, });
Future<ParseUser> currentUser();
Future<bool> logout();
}
  • Create MockUserRepository extends from BaseUserRepository
class MockUserRepository extends BaseUserRepository {
ParseUser user;
bool isLogout;
MockUserRepository(); @override
Future<ParseUser> authenticate({String username, String email, String password}) {
var completer = new Completer<ParseUser>();
// At some time you need to complete the future:
completer.complete(user);
return completer.future;
}
@override
Future<ParseUser> currentUser() {
var completer = new Completer<ParseUser>();
// At some time you need to complete the future:
completer.complete(user);
return completer.future;
}
@override
Future<bool> logout() {
var completer = new Completer<bool>();
// At some time you need to complete the future:
completer.complete(isLogout);
return completer.future;
}
@override
Future<ParseUser> signup({String username, String email, String password}) {
var completer = new Completer<ParseUser>();
// At some time you need to complete the future:
completer.complete(user);
return completer.future;
}
}
  • Write a test:

Step 1: create makeWidgetTestable function.

Widget makeWidgetTestable({Widget child, LoginBloc loginBloc}) {
return MaterialApp(
home: BlocProvider<LoginBloc>(
builder: (context) {
return loginBloc;
},
child: child,
),
);
}

Step 2: load Login widget for testing

MockUserRepository mockUserRepository = MockUserRepository(); 
LoginBloc _loginBloc = LoginBloc(authBloc: AuthBloc(userRepository: mockUserRepository), userRepository: mockUserRepository);
LoginScreen screen = LoginScreen();
await tester.pumpWidget(makeWidgetTestable(child: screen, loginBloc: _loginBloc));

Step 3: your test code

_loginBloc.dispatch(LoginButtonPressed(
username: 'trongdth',
email: 'trongdth@gmail.com',
password: '123456'
));
await tester.pump();
final loginKey = Key('BtnLogin');
expect(find.byKey(loginKey), findsOneWidget);

Step 4: run the test

flutter test

Notes: all test files must have a suffix test at the end.

There are many devs ask me why I choose back4app as server-side and why don’t use Amazon or Google cloud. So here is my answer:

  • Serverless: you (a mobile developer) can control your data, not depend on Backend and DevOps anymore.
  • Pricing: cheaper than AWS.
  • Push notification: send push notification at scale.

Any feedbacks are welcome! For viewing the complete code go to:

📝 Read this story later in Journal.

👩‍💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.

--

--

Trong Dinh Thai Hoang

I’m a peaceful person who wants to make friend with people around the world.