Flutter testing
This section will introduce three types of testing in Flutter: unit tests, widget tests, and integration tests.
Test Types Overview
| Type | Description | Run commands |
|---|---|---|
| Unit Testing | Testing individual functions and class methods | flutter test |
| Widget Testing | Test the UI of a single Widget | flutter test |
| Integration Testing | Testing the complete application flow | flutter test integration_test/ |
Unit Testing
Unit tests are used to test business logic code.
Example: Unit Test Example
// math_utils.dart - code to be tested
class MathUtils {
static int add(int a, int b) => a + b;
static int subtract(int a, int b) => a - b;
static int multiply(int a, int b) => a * b;
static double divide(int a, int b) {
if (b == 0) throw ArgumentError(Cannot divide by zero);
return a / b;
}
static int factorial(int n) {
if (n < 0) throw ArgumentError(Negative numbers do not have factorials);
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
}
// math_utils_test.dart - test file
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/math_utils.dart';
void main() {
group('MathUtils', () {
test(add adds two numbers, () {
expect(MathUtils.add(2, 3), equals(5));
});
test(subtract subtracts two numbers, () {
expect(MathUtils.subtract(5, 3), equals(2));
});
test(multiply multiplies two numbers, () {
expect(MathUtils.multiply(4, 5), equals(20));
});
test(divide divides two numbers, () {
expect(MathUtils.divide(10, 2), equals(5));
});
test(divide throws exception when dividing by zero, () {
expect(() => MathUtils.divide(10, 0), throwsArgumentError);
});
test(factorial calculates factorial, () {
expect(MathUtils.factorial(5), equals(120));
expect(MathUtils.factorial(0), equals(1));
});
test(factorial throws exception for negative numbers, () {
expect(() => MathUtils.factorial(-1), throwsArgumentError);
});
});
}
class MathUtils {
static int add(int a, int b) => a + b;
static int subtract(int a, int b) => a - b;
static int multiply(int a, int b) => a * b;
static double divide(int a, int b) {
if (b == 0) throw ArgumentError(Cannot divide by zero);
return a / b;
}
static int factorial(int n) {
if (n < 0) throw ArgumentError(Negative numbers do not have factorials);
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
}
// math_utils_test.dart - test file
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/math_utils.dart';
void main() {
group('MathUtils', () {
test(add adds two numbers, () {
expect(MathUtils.add(2, 3), equals(5));
});
test(subtract subtracts two numbers, () {
expect(MathUtils.subtract(5, 3), equals(2));
});
test(multiply multiplies two numbers, () {
expect(MathUtils.multiply(4, 5), equals(20));
});
test(divide divides two numbers, () {
expect(MathUtils.divide(10, 2), equals(5));
});
test(divide throws exception when dividing by zero, () {
expect(() => MathUtils.divide(10, 0), throwsArgumentError);
});
test(factorial calculates factorial, () {
expect(MathUtils.factorial(5), equals(120));
expect(MathUtils.factorial(0), equals(1));
});
test(factorial throws exception for negative numbers, () {
expect(() => MathUtils.factorial(-1), throwsArgumentError);
});
});
}
Widget Testing
Widget tests are used to test UI components.
Example: Widget Test
// counter_widget.dart - Widget under test
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(Count: $_count, key: const Key('counter_text')),
ElevatedButton(
key: const Key('increment_button'),
onPressed: _increment,
child: const Text('Increase'),
),
],
);
}
}
// counter_widget_test.dart - test file
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_widget.dart';
void main() {
testWidgets(CounterWidget displays initial count, (WidgetTester tester) async {
// Build Widget
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// Verify initial count display
expect(find.text(Count: 0), findsOneWidget);
});
testWidgets(CounterWidget increments count on button click, (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// Find and click the button
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
// Verify count has been updated
expect(find.text(Count: 1), findsOneWidget);
// Click again
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
expect(find.text(Count: 2), findsOneWidget);
});
}
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(Count: $_count, key: const Key('counter_text')),
ElevatedButton(
key: const Key('increment_button'),
onPressed: _increment,
child: const Text('Increase'),
),
],
);
}
}
// counter_widget_test.dart - test file
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_widget.dart';
void main() {
testWidgets(CounterWidget displays initial count, (WidgetTester tester) async {
// Build Widget
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// Verify initial count display
expect(find.text(Count: 0), findsOneWidget);
});
testWidgets(CounterWidget increments count on button click, (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// Find and click the button
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
// Verify count has been updated
expect(find.text(Count: 1), findsOneWidget);
// Click again
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
expect(find.text(Count: 2), findsOneWidget);
});
}
Integration Testing
Integration tests are used to test the complete application functionality flow.
Example: Integration Test
// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group(App integration test, () {
testWidgets(Full user flow test, (WidgetTester tester) async {
// Launch app
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
// Verify home page is displayed
expect(find.text('Home'), findsOneWidget);
// Click to navigate to the detail page
await tester.tap(find.text('Details'));
await tester.pumpAndSettle();
// Verify detail page display
expect(find.text('Detail Page'), findsOneWidget);
// Click back
await tester.tap(find.byIcon(Icons.arrow_back));
await tester.pumpAndSettle();
// Verify return to home
expect(find.text('Home'), findsOneWidget);
});
});
}
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group(App integration test, () {
testWidgets(Full user flow test, (WidgetTester tester) async {
// Launch app
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
// Verify home page is displayed
expect(find.text('Home'), findsOneWidget);
// Click to navigate to the detail page
await tester.tap(find.text('Details'));
await tester.pumpAndSettle();
// Verify detail page display
expect(find.text('Detail Page'), findsOneWidget);
// Click back
await tester.tap(find.byIcon(Icons.arrow_back));
await tester.pumpAndSettle();
// Verify return to home
expect(find.text('Home'), findsOneWidget);
});
});
}
other extensionsGood test coverage can improve code quality; it is recommended to write unit tests for at least the core business logic.