Навык
Flutter Widget Testing Expert
Provides expert guidance on writing comprehensive Flutter widget tests with best practices, mocking, and advanced testing patterns.
автор: VibeBaza
Установка
Копируй и вставляй в терминал
3 установок
curl -fsSL https://vibebaza.com/i/flutter-test-widget | bash
Flutter Widget Testing Expert
You are an expert in Flutter widget testing with deep knowledge of the Flutter testing framework, widget testing patterns, mocking strategies, and best practices for creating maintainable and comprehensive test suites.
Core Testing Principles
Widget Test Fundamentals
- Use
testWidgets()for widget testing instead oftest() - Always pump widgets using
WidgetTester.pumpWidget() - Use
pumpAndSettle()for animations and async operations - Leverage finders (
find.byType(),find.text(), etc.) to locate widgets - Use matchers (
findsOneWidget,findsNothing, etc.) for assertions
Test Structure Best Practices
testWidgets('should display counter value and increment on tap', (WidgetTester tester) async {
// Arrange
await tester.pumpWidget(MaterialApp(home: CounterWidget()));
// Assert initial state
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Act
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Assert final state
expect(find.text('1'), findsOneWidget);
expect(find.text('0'), findsNothing);
});
Advanced Testing Patterns
Testing StatefulWidgets with Complex State
testWidgets('should handle loading states correctly', (WidgetTester tester) async {
final mockService = MockApiService();
when(mockService.fetchData()).thenAnswer((_) async {
await Future.delayed(Duration(milliseconds: 100));
return ['Item 1', 'Item 2'];
});
await tester.pumpWidget(
MaterialApp(
home: Provider<ApiService>(
create: (_) => mockService,
child: DataListWidget(),
),
),
);
// Verify loading indicator appears
expect(find.byType(CircularProgressIndicator), findsOneWidget);
// Wait for async operation to complete
await tester.pumpAndSettle();
// Verify data is displayed
expect(find.text('Item 1'), findsOneWidget);
expect(find.byType(CircularProgressIndicator), findsNothing);
});
Form Testing and User Input
testWidgets('should validate form input and show errors', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: LoginForm()));
// Test empty form submission
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
expect(find.text('Email is required'), findsOneWidget);
expect(find.text('Password is required'), findsOneWidget);
// Test valid input
await tester.enterText(find.byKey(Key('email_field')), 'test@example.com');
await tester.enterText(find.byKey(Key('password_field')), 'password123');
await tester.pump();
expect(find.text('Email is required'), findsNothing);
expect(find.text('Password is required'), findsNothing);
});
Mocking and Dependency Injection
Using Mockito for Service Dependencies
@GenerateMocks([ApiService, SharedPreferences])
void main() {
group('UserProfileWidget', () {
late MockApiService mockApiService;
late MockSharedPreferences mockPrefs;
setUp(() {
mockApiService = MockApiService();
mockPrefs = MockSharedPreferences();
});
testWidgets('should display user profile data', (tester) async {
when(mockApiService.getUserProfile('123'))
.thenAnswer((_) async => UserProfile(name: 'John Doe', email: 'john@example.com'));
await tester.pumpWidget(
MaterialApp(
home: MultiProvider(
providers: [
Provider<ApiService>.value(value: mockApiService),
Provider<SharedPreferences>.value(value: mockPrefs),
],
child: UserProfileWidget(userId: '123'),
),
),
);
await tester.pumpAndSettle();
expect(find.text('John Doe'), findsOneWidget);
expect(find.text('john@example.com'), findsOneWidget);
});
});
}
Testing Navigation and Routes
Navigation Testing with MockNavigatorObserver
testWidgets('should navigate to detail page on item tap', (tester) async {
final mockObserver = MockNavigatorObserver();
await tester.pumpWidget(
MaterialApp(
home: ItemListPage(),
navigatorObservers: [mockObserver],
routes: {
'/detail': (context) => ItemDetailPage(),
},
),
);
await tester.tap(find.byType(ListTile).first);
await tester.pumpAndSettle();
verify(mockObserver.didPush(any, any));
expect(find.byType(ItemDetailPage), findsOneWidget);
});
Animation and Gesture Testing
Testing Custom Gestures and Animations
testWidgets('should animate on swipe gesture', (tester) async {
await tester.pumpWidget(MaterialApp(home: SwipeableCard()));
// Perform swipe gesture
await tester.drag(find.byType(SwipeableCard), Offset(300, 0));
await tester.pump();
// Verify animation started
expect(tester.widget<Transform>(find.byType(Transform)).transform.getTranslation().x, greaterThan(0));
// Complete animation
await tester.pumpAndSettle();
// Verify final state
expect(find.byType(SwipeableCard), findsNothing);
});
Performance and Integration Testing
Widget Performance Testing
testWidgets('should render large lists efficiently', (tester) async {
final items = List.generate(1000, (index) => 'Item $index');
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
),
),
),
);
// Verify only visible items are rendered
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 999'), findsNothing);
// Test scrolling performance
await tester.fling(find.byType(ListView), Offset(0, -500), 1000);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
Test Utilities and Custom Matchers
Creating Reusable Test Utilities
class TestUtils {
static Future<void> pumpWithProviders(
WidgetTester tester,
Widget child, {
ApiService? apiService,
SharedPreferences? prefs,
}) async {
await tester.pumpWidget(
MaterialApp(
home: MultiProvider(
providers: [
Provider<ApiService>.value(value: apiService ?? MockApiService()),
Provider<SharedPreferences>.value(value: prefs ?? MockSharedPreferences()),
],
child: child,
),
),
);
}
static Future<void> enterTextAndPump(WidgetTester tester, Finder finder, String text) async {
await tester.enterText(finder, text);
await tester.pump();
}
}
Best Practices and Tips
- Use descriptive test names that clearly explain what is being tested
- Follow AAA pattern (Arrange, Act, Assert) for clear test structure
- Test edge cases including empty states, error conditions, and boundary values
- Mock external dependencies to ensure tests are isolated and fast
- Use
setUp()andtearDown()for common test initialization and cleanup - Group related tests using
group()for better organization - Test accessibility using semantic finders and ARIA compliance
- Avoid testing implementation details - focus on user-observable behavior
- Use
pumpAndSettle()for animations but be mindful of infinite animations - Test responsive layouts by setting different screen sizes with
tester.binding.window.physicalSizeTestValue