Testing
Testing ក្នុង Flutter
Testing ជួយធានាថា code ដំណើរការត្រឹមត្រូវ និងកាត់បន្ថយ bugs។ Flutter support 3 levels of testing: Unit Tests, Widget Tests, និង Integration Tests។
📚 Testing Pyramid Theory
Testing Pyramid (Bottom to Top)
/\
/ \
/ E2E\ ← តិចបំផុត (slow, expensive)
/------\
/ \
/ Widget \ ← មធ្យម
/ Tests \
/------------\
/ \
/ Unit Tests \ ← ច្រើនបំផុត (fast, cheap)
/________________\
70% Unit Tests - Test business logic
20% Widget Tests - Test UI components
10% E2E Tests - Test full user flows
Testing Types:
Test Type | What to Test | Speed | Cost |
---|---|---|---|
Unit Tests | Functions, classes, methods | ⚡ Very Fast | 💰 Cheap |
Widget Tests | UI components, user interactions | ⚡⚡ Medium | 💰💰 Medium |
Integration Tests | Complete app flows, E2E scenarios | 🐌 Slow | 💰💰💰 Expensive |
✅ Unit Testing (Business Logic)
Test pure Dart code without UI dependencies.
# pubspec.yaml
dev_dependencies:
test: ^1.24.0
import 'package:test/test.dart';
void main() {
test('Counter increments', () {
final counter = Counter();
counter.increment();
expect(counter.count, 1);
});
group('Counter class', () {
late Counter counter;
setUp(() {
// Run before each test
counter = Counter();
});
test('starts at 0', () {
expect(counter.count, 0);
});
test('increments by 1', () {
counter.increment();
expect(counter.count, 1);
});
test('decrements by 1', () {
counter.increment();
counter.decrement();
expect(counter.count, 0);
});
test('cannot go below 0', () {
counter.decrement();
expect(counter.count, 0);
});
});
}
// Run tests:
// flutter test test/counter_test.dart
Common Matchers:
// Equality
expect(actual, equals(expected));
expect(value, 42);
expect(name, 'John');
// Types
expect(value, isA<int>());
expect(list, isList);
// Numbers
expect(value, greaterThan(10));
expect(value, lessThanOrEqualTo(100));
expect(value, closeTo(3.14, 0.01));
// Strings
expect(text, contains('hello'));
expect(text, startsWith('Hi'));
expect(text, matches(r'\d+'));
// Collections
expect(list, isEmpty);
expect(list, hasLength(3));
expect(list, contains(5));
// Booleans
expect(value, isTrue);
expect(value, isFalse);
expect(value, isNull);
// Exceptions
expect(() => throwError(), throwsException);
expect(() => divide(10, 0), throwsA(isA<ArgumentError>()));
🎨 Widget Testing (UI Components)
Test Flutter widgets and user interactions.
# pubspec.yaml (already included)
dev_dependencies:
flutter_test:
sdk: flutter
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter widget test', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('1'), findsOneWidget);
});
testWidgets('Button displays correct text', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ElevatedButton(
onPressed: () {},
child: Text('Click Me'),
),
),
),
);
expect(find.text('Click Me'), findsOneWidget);
expect(find.byType(ElevatedButton), findsOneWidget);
});
testWidgets('TextField input works', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: TextField(
key: Key('email_field'),
),
),
),
);
// Enter text
await tester.enterText(find.byKey(Key('email_field')), 'test@example.com');
await tester.pump();
expect(find.text('test@example.com'), findsOneWidget);
});
}
// test/widget_test.dart
Widget Test Utilities:
// Finders
find.text('Hello'); // Find by text
find.byKey(Key('my_widget')); // Find by key
find.byType(ElevatedButton); // Find by widget type
find.byIcon(Icons.add); // Find by icon
find.widgetWithText(ElevatedButton, 'Submit'); // Specific widget with text
// Actions
await tester.tap(find.byIcon(Icons.add)); // Tap widget
await tester.longPress(find.text('Item')); // Long press
await tester.drag(find.byType(ListView), Offset(0, -200)); // Scroll
await tester.enterText(find.byType(TextField), 'Text'); // Type text
// Waiting
await tester.pump(); // Rebuild once
await tester.pumpAndSettle(); // Wait for animations
await tester.pump(Duration(seconds: 1)); // Wait duration
// Matchers
expect(find.text('Hello'), findsOneWidget); // Finds exactly one
expect(find.byType(Text), findsWidgets); // Finds at least one
expect(find.text('Missing'), findsNothing); // Finds none
expect(find.byType(ListTile), findsNWidgets(5)); // Finds exactly N
🔗 Integration Testing (E2E)
Test complete app flows on real devices or emulators.
# pubspec.yaml
dev_dependencies:
integration_test:
sdk: flutter
// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Complete login flow', (tester) async {
app.main();
await tester.pumpAndSettle();
// Navigate to login
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
// Enter credentials
await tester.enterText(find.byKey(Key('email')), 'user@example.com');
await tester.enterText(find.byKey(Key('password')), 'password123');
// Submit
await tester.tap(find.text('Submit'));
await tester.pumpAndSettle();
// Verify navigation to home
expect(find.text('Welcome'), findsOneWidget);
});
}
// Run: flutter test integration_test/app_test.dart
🎯 Mocking & Test Doubles
# pubspec.yaml
dev_dependencies:
mockito: ^5.4.0
build_runner: ^2.4.0
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
// 1. Create mock class
@GenerateMocks([ApiService])
void main() {}
// 2. Run code generation
// flutter pub run build_runner build
// 3. Use mock in tests
import 'api_test.mocks.dart';
void main() {
test('fetchUser returns user data', () async {
// Create mock
final mockApi = MockApiService();
// Setup mock behavior
when(mockApi.fetchUser(1)).thenAnswer(
(_) async => User(id: 1, name: 'John'),
);
// Test
final user = await mockApi.fetchUser(1);
// Verify
expect(user.name, 'John');
verify(mockApi.fetchUser(1)).called(1);
});
}
📊 Test Coverage
# Generate coverage report
flutter test --coverage
# View coverage (requires lcov)
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
✅ Testing Best Practices
1. AAA Pattern (Arrange-Act-Assert)
test('calculator adds two numbers', () {
// Arrange - Setup
final calculator = Calculator();
// Act - Execute
final result = calculator.add(2, 3);
// Assert - Verify
expect(result, 5);
});
2. Test Naming Convention
// ❌ Bad
test('test1', () {});
// ✅ Good
test('Counter increments when button is tapped', () {});
test('Login fails with invalid email', () {});
test('Product list shows 10 items', () {});
3. Golden Tests (Screenshot Testing)
testWidgets('Golden test for MyWidget', (tester) async {
await tester.pumpWidget(MyWidget());
await expectLater(
find.byType(MyWidget),
matchesGoldenFile('goldens/my_widget.png'),
);
});
// Update goldens:
// flutter test --update-goldens
💡 Best Practices:
- ✅ Write tests before fixing bugs (regression tests)
- ✅ Aim for 70%+ code coverage
- ✅ Test edge cases and error conditions
- ✅ Keep tests simple and focused (one concept per test)
- ✅ Use descriptive test names
- ✅ Run tests in CI/CD pipeline
- ✅ Mock external dependencies (APIs, databases)
⚠️ Testing Mistakes:
- ❌ Testing implementation details instead of behavior
- ❌ Writing tests that depend on other tests
- ❌ Not testing error cases
- ❌ Slow tests (optimize with mocks)
- ❌ Testing private methods (test public API)
💡 ជំនួយ: សរសេរ tests ដើម្បីកាត់បន្ថយ bugs។ Unit tests ពិនិត្យ business logic។ Widget tests ពិនិត្យ UI។ Integration tests ពិនិត្យ full user flows។