Complete Flutter Testing Guide
Table of Contents
1. Testing Fundamentals
2. Unit Tests
3. Widget Tests
4. Integration Tests
5. Best Practices
6. Common Interview Questions
Testing Fundamentals
Why Testing Matters
Catch bugs early: Find issues before users do
Refactoring confidence: Change code without breaking functionality
Documentation: Tests show how code should work
Code quality: Forces you to write testable, modular code
Testing Pyramid
/\
/ \ Integration Tests (Few - Slow, Expensive)
/____\
/ \
/ Widget \ Widget Tests (Some - Medium speed)
/ Tests \
/___________\
/ \
/ Unit Tests \ Unit Tests (Many - Fast, Cheap)
/_______________\
Setup
Add to [Link]:
yaml
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.4.0
build_runner: ^2.4.0
integration_test:
sdk: flutter
Unit Tests
What are Unit Tests?
Tests for individual functions, methods, or classes in isolation. They're fast and test business logic.
File Structure
lib/
models/
[Link]
services/
api_service.dart
test/
models/
user_test.dart
services/
api_service_test.dart
Basic Unit Test Example
dart
// lib/models/[Link]
class Calculator {
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
double divide(int a, int b) {
if (b == 0) throw ArgumentError('Cannot divide by zero');
return a / b;
}
}
// test/models/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/models/[Link]';
void main() {
// Test suite
group('Calculator', () {
late Calculator calculator;
// Runs before each test
setUp(() {
calculator = Calculator();
} );
// Individual test
test('should add two numbers correctly', () {
// Arrange
final a = 5;
final b = 3;
// Act
final result = [Link](a, b);
// Assert
expect(result, 8);
} );
test('should subtract two numbers correctly', () {
expect([Link](10, 4), 6);
} );
test('should throw error when dividing by zero', () {
expect(
() => [Link](10, 0),
throwsA(isA<ArgumentError>()),
);
} );
} );
}
Mocking Dependencies
dart
// lib/services/user_service.dart
class UserService {
final ApiClient apiClient;
UserService([Link]);
Future<User> getUser(String id) async {
final response = await [Link]('/users/$id');
return [Link](response);
}
}
// test/services/user_service_test.dart
import 'package:mockito/[Link]';
import 'package:mockito/[Link]';
import 'package:flutter_test/flutter_test.dart';
// Generate mock
@GenerateMocks([ApiClient])
import 'user_service_test.[Link]';
void main() {
group('UserService', () {
late UserService userService;
late MockApiClient mockApiClient;
setUp(() {
mockApiClient = MockApiClient();
userService = UserService(mockApiClient);
} );
test('should fetch user successfully', () async {
// Arrange
final mockResponse = {'id': '1', 'name': 'John'};
when([Link]('/users/1'))
.thenAnswer((_) async => mockResponse);
// Act
final user = await [Link]('1');
// Assert
expect([Link], 'John');
verify([Link]('/users/1')).called(1);
} );
} );
}
Common Matchers
dart
expect(actual, expected); // Equals
expect(actual, isNull); // Null check
expect(actual, isNotNull); // Not null
expect(actual, isTrue); // Boolean
expect(actual, greaterThan(5)); // Comparison
expect(actual, contains('text')); // String/List contains
expect(() => func(), throwsException); // Throws exception
expect(actual, isA<Type>()); // Type check
Widget Tests
What are Widget Tests?
Tests for UI components in isolation. They test how widgets look and behave without running the full app.
Basic Widget Test
dart
// lib/widgets/counter_widget.dart
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
} );
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_counter', key: Key('counter_text')),
ElevatedButton(
key: Key('increment_button'),
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}
// test/widgets/counter_widget_test.dart
import 'package:flutter/[Link]';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/widgets/counter_widget.dart';
void main() {
testWidgets('Counter increments when button pressed', (WidgetTester tester) async {
// Build the widget
await [Link](
MaterialApp(
home: Scaffold(
body: CounterWidget(),
),
),
);
// Verify initial state
expect([Link]('Count: 0'), findsOneWidget);
expect([Link]('Count: 1'), findsNothing);
// Tap the button
await [Link]([Link](Key('increment_button')));
// Rebuild the widget with the new state
await [Link]();
// Verify the counter incremented
expect([Link]('Count: 0'), findsNothing);
expect([Link]('Count: 1'), findsOneWidget);
} );
}
Finding Widgets
dart
[Link]('Hello'); // By text
[Link](Key('my_key')); // By key (RECOMMENDED)
[Link](ElevatedButton); // By widget type
[Link]([Link]); // By icon
[Link](ElevatedButton, 'Click'); // Widget with text
[Link]( // Nested search
of: [Link](Container),
matching: [Link]('Child'),
);
Interactions
dart
await [Link]([Link](Key('button'))); // Tap
await [Link]([Link](Key('button'))); // Long press
await [Link]([Link](ListView), Offset(0, -200)); // Scroll
await [Link]([Link](Key('input')), 'text'); // Type text
// Rebuild after interaction
await [Link](); // Single frame
await [Link](); // Until animations complete
await [Link](Duration(seconds: 1)); // Specific duration
Testing with Dependencies
dart
testWidgets('Shows user data from service', (tester) async {
// Create mock
final mockUserService = MockUserService();
when([Link]('1'))
.thenAnswer((_) async => User(id: '1', name: 'John'));
// Provide dependency
await [Link](
MaterialApp(
home: Provider<UserService>(
create: (_) => mockUserService,
child: UserScreen(userId: '1'),
),
),
);
await [Link]();
expect([Link]('John'), findsOneWidget);
} );
Golden Tests (Screenshot Tests)
dart
testWidgets('Golden test for button', (tester) async {
await [Link](
MaterialApp(
home: ElevatedButton(
onPressed: () {},
child: Text('Click Me'),
),
),
);
await expectLater(
[Link](ElevatedButton),
matchesGoldenFile('goldens/[Link]'),
);
} );
// Run: flutter test --update-goldens to generate images
Integration Tests
What are Integration Tests?
End-to-end tests that run the full app on a device/emulator. They test complete user flows.
Setup
dart
// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:your_app/[Link]' as app;
void main() {
[Link]();
group('App Test', () {
testWidgets('Complete login flow', (tester) async {
// Start the app
[Link]();
await [Link]();
// Verify we're on login screen
expect([Link]('Login'), findsOneWidget);
// Enter credentials
await [Link](
[Link](Key('email_field')),
'test@[Link]',
);
await [Link](
[Link](Key('password_field')),
'password123',
);
// Submit
await [Link]([Link](Key('login_button')));
await [Link]();
// Verify navigation to home
expect([Link]('Home Screen'), findsOneWidget);
} );
testWidgets('Add item to cart flow', (tester) async {
[Link]();
await [Link]();
// Navigate to products
await [Link]([Link](Icons.shopping_bag));
await [Link]();
// Add first product
await [Link]([Link](Key('add_to_cart_0')));
await [Link]();
// Go to cart
await [Link]([Link](Icons.shopping_cart));
await [Link]();
// Verify item in cart
expect([Link](Key('cart_item')), findsOneWidget);
} );
} );
}
Running Integration Tests
bash
# Run on connected device/emulator
flutter test integration_test/app_test.dart
# Run on specific device
flutter test integration_test/app_test.dart -d <device-id>
# Generate coverage
flutter test --coverage integration_test/app_test.dart
Best Practices
1. Use Keys for Widgets
dart
// ✅ Good
ElevatedButton(
key: Key('submit_button'),
child: Text('Submit'),
onPressed: () {},
)
// ❌ Bad - finding by text is fragile
ElevatedButton(
child: Text('Submit'),
onPressed: () {},
)
2. AAA Pattern (Arrange-Act-Assert)
dart
test('calculates total price', () {
// Arrange - Setup
final cart = ShoppingCart();
[Link](Item(price: 10.0));
// Act - Execute
final total = [Link]();
// Assert - Verify
expect(total, 10.0);
} );
3. Test One Thing Per Test
dart
// ✅ Good
test('should add item to list', () {
[Link](item);
expect([Link], 1);
} );
test('should remove item from list', () {
[Link](item);
[Link](item);
expect([Link], 0);
} );
// ❌ Bad - testing too much
test('should add and remove items', () {
[Link](item);
expect([Link], 1);
[Link](item);
expect([Link], 0);
} );
4. Use setUp and tearDown
dart
group('Database tests', () {
late Database db;
setUp(() async {
db = await [Link]();
} );
tearDown(() async {
await [Link]();
} );
test('inserts data', () async {
await [Link]('test');
expect(await [Link](), 1);
} );
} );
5. Test Edge Cases
dart
group('User age validation', () {
test('accepts valid age', () {
expect(validateAge(25), true);
} );
test('rejects negative age', () {
expect(validateAge(-1), false);
} );
test('rejects age over 150', () {
expect(validateAge(151), false);
} );
test('handles zero', () {
expect(validateAge(0), false);
} );
test('handles boundary values', () {
expect(validateAge(1), true);
expect(validateAge(150), true);
} );
} );
6. Mock External Dependencies
dart
// Always mock: APIs, databases, file systems, time, random values
test('fetches data from API', () async {
final mockApi = MockApiClient();
when([Link]()).thenAnswer((_) async => mockData);
final result = await [Link]();
expect(result, mockData);
} );
7. Test Coverage
bash
# Generate coverage report
flutter test --coverage
# View in browser (requires lcov)
genhtml coverage/[Link] -o coverage/html
open coverage/html/[Link]
Aim for:
Unit tests: 80-90% coverage
Widget tests: 60-70% coverage
Integration tests: Critical user flows
Common Interview Questions
Q1: What's the difference between unit, widget, and integration tests?
Answer:
Unit tests test individual functions/classes in isolation (business logic)
Widget tests test UI components in isolation (single widgets/screens)
Integration tests test the full app end-to-end (complete user flows)
Q2: How do you mock dependencies in Flutter?
Answer: Use the mockito package with @GenerateMocks annotation to generate mock classes, then use when() to stub
methods and verify() to check calls.
Q3: What is pumpAndSettle()?
Answer: It repeatedly rebuilds the widget tree until all animations complete and the UI stabilizes. Use it after
interactions that trigger animations or async operations.
Q4: How do you test async code?
Answer: Mark test functions as async and use await:
dart
test('fetches data', () async {
final data = await fetchData();
expect(data, isNotNull);
} );
Q5: What are golden tests?
Answer: Snapshot tests that compare rendered widget screenshots against baseline images to catch visual regressions.
Q6: How do you handle setState in tests?
Answer: Call await [Link]() after interactions that trigger setState() to rebuild the widget tree.
Q7: What's the purpose of Keys?
Answer: Keys help identify and find specific widgets in tests. They provide stable references that don't depend on text or
position.
Q8: How do you test navigation?
Answer: Use [Link]() and verify the new route appears:
dart
await [Link]([Link](Key('login_button')));
await [Link]();
expect([Link](HomeScreen), findsOneWidget);
Q9: What's code coverage and what's a good target?
Answer: Percentage of code executed by tests. Good targets: 80%+ for business logic, 60-70% for UI, 100% for critical
paths.
Q10: How do you test widgets that use InheritedWidget/Provider?
Answer: Wrap the widget under test with the provider:
dart
await [Link](
Provider<MyService>(
create: (_) => mockService,
child: MyWidget(),
),
);
Quick Reference
Test Commands
bash
flutter test # Run all tests
flutter test test/models/ # Run specific folder
flutter test test/user_test.dart # Run specific file
flutter test --coverage # With coverage
flutter test integration_test/ # Run integration tests
Common Test Structure
dart
void main() {
group('Feature Name', () {
late MyClass instance;
setUp(() {
instance = MyClass();
} );
tearDown(() {
[Link]();
} );
test('should do something', () {
// Arrange
final input = 'test';
// Act
final result = [Link](input);
// Assert
expect(result, expectedValue);
} );
} );
}
Testing Checklist
✅ Test business logic (unit tests)
✅ Test UI components (widget tests)
✅ Test critical user flows (integration tests)
✅ Test edge cases and error handling
✅ Mock external dependencies
✅ Use descriptive test names
✅ Keep tests independent
✅ Aim for good coverage
✅ Run tests before commits
Additional Resources
Official Flutter Testing Docs: [Link]
Mockito Package: [Link]
Integration Testing: [Link]
Good luck with your interviews! 🚀