How to do test your Flutter app
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.
In this article, I only focus on unit test and widget test. The integration test should be another thread.
3. 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
undertest
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 fromBaseUserRepository
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.