Testing Flutter Apps with Mock Data
•8 min read
Testing is a crucial aspect of Flutter app development that ensures reliability and maintainability. This comprehensive guide will walk you through various testing strategies using mock data, from basic unit tests to complex integration tests.
Understanding Mock Data in Testing
1. Why Use Mock Data?
- Isolation: Test components independently
- Predictability: Control test scenarios
- Speed: Faster test execution
- Reliability: Consistent test results
- Edge Cases: Test error conditions
2. Types of Tests
- Unit Tests: Test individual functions/classes
- Widget Tests: Test UI components
- Integration Tests: Test app flows
- Golden Tests: Test visual appearance
Setting Up Testing Environment
1. Required Packages
dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 build_runner: ^2.0.0 integration_test: sdk: flutter flutter_driver: sdk: flutter golden_toolkit: ^0.12.0
2. Mock Data Generation
// test/mocks/user_mock.dart import 'package:mockito/annotations.dart'; import 'package:your_app/services/api_service.dart'; import 'package:your_app/services/database_service.dart'; import 'package:your_app/services/auth_service.dart'; @GenerateMocks([ ApiService, DatabaseService, AuthService, ]) void main() {}
Run the generator:
flutter pub run build_runner build
Unit Testing with Mock Data
1. Basic Mock Setup
import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:your_app/services/api_service.dart'; import 'package:your_app/models/user.dart'; import 'mocks/api_service_test.mocks.dart'; void main() { group('ApiService Tests', () { late MockApiService mockApiService; late UserRepository userRepository; setUp(() { mockApiService = MockApiService(); userRepository = UserRepository(apiService: mockApiService); }); test('Fetch users returns a list of users', () async { // Arrange final mockUsers = [ User(id: 1, name: 'John Doe', email: 'john.doe@example.com'), User(id: 2, name: 'Jane Smith', email: 'jane.smith@example.com'), ]; when(mockApiService.fetchUsers()).thenAnswer((_) async => mockUsers); // Act final users = await userRepository.getUsers(); // Assert expect(users, isNotEmpty); expect(users.length, 2); expect(users.first.name, 'John Doe'); verify(mockApiService.fetchUsers()).called(1); }); test('Handles API errors gracefully', () async { // Arrange when(mockApiService.fetchUsers()) .thenThrow(ApiException('Network error')); // Act & Assert expect( () => userRepository.getUsers(), throwsA(isA<ApiException>()), ); }); }); }
2. Advanced Mocking Techniques
// Mocking streams test('Stream subscription test', () { final mockStream = StreamController<User>(); when(mockApiService.userStream()).thenAnswer((_) => mockStream.stream); // Test stream behavior mockStream.add(User(id: 1, name: 'Test User')); // Verify stream handling }); // Mocking complex objects test('Complex object mocking', () { final mockUser = MockUser(); when(mockUser.name).thenReturn('Mock User'); when(mockUser.isActive).thenReturn(true); // Test with mocked object });
Widget Testing with Mock Data
1. Basic Widget Test
import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:your_app/widgets/user_list.dart'; import 'package:your_app/services/user_service.dart'; import 'mocks/user_service_test.mocks.dart'; void main() { group('UserList Widget Tests', () { late MockUserService mockUserService; setUp(() { mockUserService = MockUserService(); }); testWidgets('Displays list of users', (WidgetTester tester) async { // Arrange final mockUsers = [ User(id: 1, name: 'John Doe'), User(id: 2, name: 'Jane Smith'), ]; when(mockUserService.getUsers()).thenAnswer((_) async => mockUsers); // Act await tester.pumpWidget( MaterialApp( home: UserList(userService: mockUserService), ), ); await tester.pumpAndSettle(); // Assert expect(find.text('John Doe'), findsOneWidget); expect(find.text('Jane Smith'), findsOneWidget); }); testWidgets('Shows loading indicator', (WidgetTester tester) async { // Arrange when(mockUserService.getUsers()) .thenAnswer((_) => Future.delayed(Duration(seconds: 1))); // Act await tester.pumpWidget( MaterialApp( home: UserList(userService: mockUserService), ), ); await tester.pump(); // Assert expect(find.byType(CircularProgressIndicator), findsOneWidget); }); }); }
2. Testing User Interactions
testWidgets('Handles user tap', (WidgetTester tester) async { // Arrange final mockUsers = [User(id: 1, name: 'Test User')]; when(mockUserService.getUsers()).thenAnswer((_) async => mockUsers); // Act await tester.pumpWidget( MaterialApp( home: UserList(userService: mockUserService), ), ); await tester.pumpAndSettle(); // Tap on user await tester.tap(find.text('Test User')); await tester.pumpAndSettle(); // Assert expect(find.byType(UserDetails), findsOneWidget); });
Integration Testing with Mock Data
1. Basic Integration Test
import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:your_app/main.dart' as app; import 'package:your_app/services/api_service.dart'; import 'mocks/api_service_test.mocks.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('App Integration Tests', () { late MockApiService mockApiService; setUp(() { mockApiService = MockApiService(); // Inject mock service app.apiService = mockApiService; }); testWidgets('Complete user flow', (WidgetTester tester) async { // Arrange final mockUsers = [ User(id: 1, name: 'John Doe'), User(id: 2, name: 'Jane Smith'), ]; when(mockApiService.fetchUsers()).thenAnswer((_) async => mockUsers); // Act app.main(); await tester.pumpAndSettle(); // Assert expect(find.text('John Doe'), findsOneWidget); expect(find.text('Jane Smith'), findsOneWidget); // Test navigation await tester.tap(find.text('John Doe')); await tester.pumpAndSettle(); expect(find.byType(UserDetails), findsOneWidget); }); }); }
2. Testing Network Calls
testWidgets('Handles network errors', (WidgetTester tester) async { // Arrange when(mockApiService.fetchUsers()) .thenThrow(ApiException('Network error')); // Act app.main(); await tester.pumpAndSettle(); // Assert expect(find.text('Error loading users'), findsOneWidget); expect(find.byType(RetryButton), findsOneWidget); });
Best Practices
1. Mock Data Organization
// test/mock_data/user_mock_data.dart class UserMockData { static List<User> get mockUsers => [ User(id: 1, name: 'John Doe'), User(id: 2, name: 'Jane Smith'), ]; static User get mockUser => User( id: 1, name: 'Test User', email: 'test@example.com', ); }
2. Test Data Factories
// test/factories/user_factory.dart class UserFactory { static User createUser({ int? id, String? name, String? email, }) { return User( id: id ?? 1, name: name ?? 'Test User', email: email ?? 'test@example.com', ); } }
3. Test Helpers
// test/helpers/test_helpers.dart extension WidgetTesterExtension on WidgetTester { Future<void> pumpUntilFound( Finder finder, { Duration timeout = const Duration(seconds: 30), }) async { bool timerDone = false; final timer = Timer(timeout, () => timerDone = true); while (!timerDone) { await pump(); if (finder.evaluate().isNotEmpty) { timer.cancel(); return; } } throw Exception('Widget not found after timeout'); } }
Common Testing Patterns
1. Testing State Changes
test('State changes correctly', () { final bloc = UserBloc(); expect( bloc.stream, emitsInOrder([ UserInitial(), UserLoading(), UserLoaded(mockUsers), ]), ); bloc.add(FetchUsers()); });
2. Testing Error Handling
test('Handles errors correctly', () { when(mockApiService.fetchUsers()) .thenThrow(ApiException('Test error')); expect( bloc.stream, emitsInOrder([ UserInitial(), UserLoading(), UserError('Test error'), ]), ); bloc.add(FetchUsers()); });
Conclusion
Effective testing with mock data requires:
- Proper setup of testing environment
- Understanding different types of tests
- Using appropriate mocking techniques
- Following best practices
- Maintaining test data organization
Remember to:
- Keep tests isolated and independent
- Use meaningful mock data
- Test edge cases and error conditions
- Maintain test readability
- Update tests with code changes
By following these guidelines and implementing the provided examples, you can build a robust testing strategy for your Flutter applications.