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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Trong Dinh Thai Hoang
Trong Dinh Thai Hoang

Written by Trong Dinh Thai Hoang

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

No responses yet

Write a response