mcpField
F
flutter-testing-skill

flutter-testing-skill

github.com/lambdatestTesting & quality384233 installsUpdated 2w ago
Repository

About flutter-testing-skill

flutter-testing-skill is a testing guide for Flutter developers, covering unit, widget, integration, and golden tests. It provides Dart examples, testing commands, and guidance on finders, interactions, and mocked dependencies.

Key features

  1. 01Unit, widget, and integration test guidance
  2. 02Widget finders and interaction patterns
  3. 03Golden tests for visual regression
  4. 04Mocking dependencies with Mockito
  5. 05Commands for running and managing tests

Use cases

  1. 01Testing Flutter UI behavior and interactions
  2. 02Checking screens against golden images
  3. 03Running integration tests or generating mocks

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/lambdatest/agent-skills --skill flutter-testing-skill

After installing, type/flutter-testing-skillin Claude Code to confirm it loaded.

Bundled filesreference/advanced-patterns.mdreference/playbook.md

Details

From the project README

Flutter Testing Skill

You are a senior Flutter developer specializing in testing.

Step 1 — Test Type

├─ "unit test", "business logic", "model test"
│  └─ Unit test: test/ directory, flutter_test package
│
├─ "widget test", "component test", "UI test"
│  └─ Widget test: test/ directory, testWidgets()
│
├─ "integration test", "E2E", "full app test"
│  └─ Integration test: integration_test/ directory
│
├─ "golden test", "snapshot", "visual regression"
│  └─ Golden test: matchesGoldenFile()
│
└─ Ambiguous? → Widget test (most common)

Core Patterns — Dart

Widget Test (Most Common)
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/screens/login_screen.dart';

void main() {
  testWidgets('Login screen shows email and password fields', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(home: LoginScreen()));

    // Verify fields exist
    expect(find.byType(TextField), findsNWidgets(2));
    expect(find.text('Email'), findsOneWidget);
    expect(find.text('Password'), findsOneWidget);
    expect(find.byType(ElevatedButton), findsOneWidget);
  });

  testWidgets('Login with valid credentials navigates to dashboard', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(home: LoginScreen()));

    // Enter credentials
    await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
    await tester.enterText(find.byKey(const Key('passwordField')), 'password123');

    // Tap login button
    await tester.tap(find.byKey(const Key('loginButton')));
    await tester.pumpAndSettle(); // Wait for animations and navigation

    // Verify navigation
    expect(find.text('Dashboard'), findsOneWidget);
  });

  testWidgets('Shows error for invalid credentials', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(home: LoginScreen()));

    await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
    await tester.enterText(find.byKey(const Key('passwordField')), 'wrong');
    await tester.tap(find.byKey(const Key('loginButton')));
    await tester.pumpAndSettle();

    expect(find.text('Invalid credentials'), findsOneWidget);
  });
}
Finder Strategies
// By Key (best — explicit test identifiers)
find.byKey(const Key('loginButton'))
find.byKey(const ValueKey('email_input'))

// By Type
find.byType(ElevatedButton)
find.byType(TextField)
find.byType(LoginScreen)

// By Text
find.text('Login')
find.textContaining('Welcome')

// By Icon
find.byIcon(Icons.login)

// By Widget predicate
find.byWidgetPredicate((widget) => widget is Text && widget.data!.startsWith('Error'))

// Descendant/Ancestor
find.descendant(of: find.byType(AppBar), matching: find.text('Title'))
find.ancestor(of: find.text('Login'), matching: find.byType(Card))
Actions
await tester.tap(finder);                    // Tap
await tester.longPress(finder);              // Long press
await tester.enterText(finder, 'text');      // Type text
await tester.drag(finder, const Offset(0, -300));  // Drag/scroll
await tester.fling(finder, const Offset(0, -500), 1000); // Fling/swipe

// CRITICAL: Always pump after actions
await tester.pump();                         // Single frame
await tester.pump(const Duration(seconds: 1)); // Advance time
await tester.pumpAndSettle();                // Wait for animations to finish
Integration Test
// 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('Full login flow', (WidgetTester tester) async {
    app.main();
    await tester.pumpAndSettle();

    // Login
    await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
    await tester.enterText(find.byKey(const Key('passwordField')), 'password123');
    await tester.tap(find.byKey(const Key('loginButton')));
    await tester.pumpAndSettle();

    // Verify dashboard
    expect(find.text('Dashboard'), findsOneWidget);

    // Navigate to settings
    await tester.tap(find.byIcon(Icons.settings));
    await tester.pumpAndSettle();
    expect(find.text('Settings'), findsOneWidget);
  });
}
Golden Tests (Visual Regression)
testWidgets('Login screen matches golden', (WidgetTester tester) async {
  await tester.pumpWidget(const MaterialApp(home: LoginScreen()));
  await tester.pumpAndSettle();

  await expectLater(
    find.byType(LoginScreen),
    matchesGoldenFile('goldens/login_screen.png'),
  );
});
# Generate golden files
flutter test --update-goldens

# Run golden comparison
flutter test
Mocking Dependencies
// Using Mockito
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([AuthService])
void main() {
  late MockAuthService mockAuth;

  setUp(() {
    mockAuth = MockAuthService();
  });

  testWidgets('Login calls auth service', (tester) async {
    when(mockAuth.login(any, any)).thenAnswer((_) async => true);

    await tester.pumpWidget(MaterialApp(
      home: LoginScreen(authService: mockAuth),
    ));

    await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
    await tester.enterText(find.byKey(const Key('passwordField')), 'pass123');
    await tester.tap(find.byKey(const Key('loginButton')));
    await tester.pumpAndSettle();

    verify(mockAuth.login('[email protected]', 'pass123')).called(1);
  });
}
Anti-Patterns
BadGoodWhy
No pumpAndSettle() after actionAlways pump after interactionsAnimations not complete
find.text() for dynamic textfind.byKey()Locale/text changes break tests
Testing implementation detailsTest user-facing behaviorBrittle
No mocking in widget testsMock services, reposTests hit real APIs
TestMu AI Cloud (Integration Tests)
# Run integration tests on LambdaTest real devices
# 1. Build app for testing
flutter build apk --debug  # Android
flutter build ios --simulator  # iOS

# 2. Upload to LambdaTest
curl -u "$LT_USERNAME:$LT_ACCESS_KEY" \
  -X POST "https://manual-api.lambdatest.com/app/upload/realDevice" \
  -F "appFile=@build/app/outputs/flutter-apk/app-debug.apk"

# 3. Run via Appium (Flutter driver)
# Use appium-flutter-driver for element interaction

Quick Reference

TaskCommand
Run all testsflutter test
Run specific fileflutter test test/login_test.dart
Run with coverageflutter test --coverage
Run integration testsflutter test integration_test/
Update goldensflutter test --update-goldens
Generate mocksflutter pub run build_runner build
Test specific platformflutter test --platform chrome

pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter
  mockito: ^5.4.0
  build_runner: ^2.4.0

Deep Patterns

For advanced patterns, debugging guides, CI/CD integration, and best practices, see reference/playbook.md.

Comments

Markdown supported