Mastering Testing Techniques in Flutter
Delve deep into the world of Flutter testing with this comprehensive overview.
Uncover the various testing techniques, including unit testing, widget testing, integration testing, and end-to-end testing, and grasp their significance in ensuring the robustness and excellence of your Flutter applications.
Whether you're new to Flutter or an experienced developer, this blog post equips you with the knowledge and resources to create flawless Flutter apps through effective testing methodologies.
- Unit Testing
- Widget Testing
- Integration Testing
- End-to-End (E2E) Testing
- Golden Testing
- Performance Testing
- Accessibility Testing
- Security Testing
- Cloud Firebase Test Lab - Google
Unit Testing
Unit testing in Flutter involves testing individual units of code, such as functions, methods, or classes, in isolation from the rest of the application. The goal is to verify that each unit of code works correctly.
Tools: Flutter's built-in test package, mockito for mocking, and other testing libraries.
Use Cases: Testing pure Dart logic, business logic, and utility functions.
Code:
lib/calculator.dart
class Calculator {
// This class represents a simple calculator.
// The `add` method takes two integers `a` and `b` as input and returns their sum.
int? add(int a, int b) {
return a + b; // Calculate the sum of `a` and `b` and return it.
}
}
..
Normal Testing
Your code has simple dependencies with known and predictable behavior.
test/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_demo/calculator.dart';
void main() {
Calculator? cal; // Declare a reference to the Calculator class
setUpAll(() {
cal =
Calculator(); // Create an instance of the Calculator class before running tests
});
group('Add function tests', () {
test('Adding two positive numbers', () {
// Arrange: Prepare the inputs
// Act: Call the add function
// Assert: Verify the result
expect(cal?.add(3, 5), equals(8));
});
test('Adding a positive number and zero', () {
expect(cal?.add(3, 0), equals(3));
});
test('Adding a negative number and a positive number', () {
expect(cal?.add(-3, 5), equals(2));
});
test('Adding two negative numbers', () {
expect(cal?.add(-3, -5), equals(-8));
});
tearDownAll(() {
// This block runs after all the tests in this group have finished.
// You can perform cleanup tasks here if needed.
print('All tests are done');
});
});
}
..
Terminal:
flutter test test/calculator_test.dart
..
Use Mock Testing when:
Your code has complex dependencies, external services, or APIs that you want to simulate.
Need to add the mockito package
dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.7
..
test/calculator_mock_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:testing_demo/calculator.dart';
class MockCalculator extends Mock implements Calculator {}
void main() {
late MockCalculator mockCalculator;
setUpAll(() {
mockCalculator = MockCalculator();
});
test('Test adding two numbers', () {
// Define the behavior of the mock
when(mockCalculator.add(3, 5)).thenReturn(8);
// Your test logic that uses the Calculator class with the mocked dependency
int? result = mockCalculator.add(3, 5);
// Verify that the mock was called with specific arguments
verify(mockCalculator.add(3, 5)).called(1);
// Verify the result
expect(result, equals(8));
});
tearDown(() {
// Clean up after each test if necessary
});
}
..
- when: This function is used to specify the behavior of a mock object when a certain method is called with specific arguments.
- thenReturn: This function is used in conjunction with when to specify the return value of a mock method when it's called with certain arguments.
- verify: This function is used to verify that a specific method on a mock object was called with certain arguments and a specific number of times
- called: This is a function used with verify to specify the number of times a method should have been called.
- expect: This function is used to make assertions in your test. You can use it to check if a certain value matches an expected value. It's commonly used to assert the results of method calls on your code under test.
..
Widget Testing
Widget testing in Flutter is focused on testing the UI components (widgets) of your app. It allows you to verify how widgets are rendered and interact with each other.
Tools: flutter_test package and WidgetTester class.
Use Cases: Testing widget behavior, layout, and interactions. It provides a higher level of confidence in the UI.
lib/main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Counter:',
key: Key('counter_text'), // Key for integration testing
),
Text(
'$_counter',
style: const TextStyle(fontSize: 24),
),
const SizedBox(height: 20),
ElevatedButton(
key: const Key('increment_button'), // Key for integration testing
onPressed: _incrementCounter,
child: const Icon(Icons.add),
),
],
),
),
);
}
}
test/widget_test.dart
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_demo/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const 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);
});
}
..
- void main(): This is the entry point of your test file. It's where your test suite starts executing.
- testWidgets(): This function is used to define a test case for a widget. It takes a callback function as an argument, which contains the actual test logic.
- (WidgetTester tester) async { ... }: This is an anonymous function that defines the test case. It takes a WidgetTester object as a parameter, which allows you to interact with and test widgets.
- await tester.pumpWidget(const MyApp());: This line builds your Flutter app (in this case, the MyApp widget) and triggers a frame to update the widget tree.
- expect(find.text('0'), findsOneWidget);: This line uses the expect function to assert that there's exactly one widget with the text '0' in the widget tree. It checks if the initial state of the counter is 0.
- expect(find.text('1'), findsNothing);: This line asserts that there's no widget with the text '1' in the widget tree at this point.
- await tester.tap(find.byIcon(Icons.add));: This simulates tapping on a widget that has the 'add' icon (in this case, the '+' icon).
- await tester.pump();: After tapping the icon, this line triggers another frame to update the widget tree.
- expect(find.text('0'), findsNothing);: This asserts that the widget with the text '0' is no longer present in the widget tree, indicating that the counter has been incremented.
- expect(find.text('1'), findsOneWidget);: This asserts that there's now one widget with the text '1' in the widget tree, confirming that the counter has incremented.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_demo/main.dart' as app;
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Test the entire app flow', (WidgetTester tester) async {
app.main(); // Start your app
await tester.pumpAndSettle(); // Wait for your app to settle
// Perform interactions and assertions here
// Example: Tap a button, expect a result
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pumpAndSettle();
expect(find.text('1'), findsOneWidget);
});
}
Comments
Post a Comment