# Flutter Integration Testing

> Use when writing Flutter integration/widget tests that verify real component interactions - covers widget tests with ProviderScope, navigation testing, form interaction, async state verification, and E2E flows. Triggers on keywords like "integration test", "widget test", "e2e test", "整合測試", "端對端測試".

- **Type:** Skill
- **Install:** `agentstack add skill-iml1s-flutter-claude-skills-flutter-integration-testing`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ImL1s](https://agentstack.voostack.com/s/iml1s)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ImL1s](https://github.com/ImL1s)
- **Source:** https://github.com/ImL1s/flutter-claude-skills/tree/main/skills/flutter-integration-testing

## Install

```sh
agentstack add skill-iml1s-flutter-claude-skills-flutter-integration-testing
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Flutter Integration Testing That Catches Real Bugs

## Core Principle

> **Integration tests verify that components work TOGETHER correctly.**
> Unit tests prove functions work in isolation. Integration tests prove THE SYSTEM works.

## When to Use Integration vs Unit Tests

| Use Unit Test | Use Integration Test |
|---------------|---------------------|
| Pure logic functions | Widget renders correctly with provider data |
| Data transformations | Navigation between pages works |
| State machine transitions | User interaction flows (tap → state change → UI update) |
| Error message formatting | Multiple providers interacting correctly |
| Single provider logic | Form submission → validation → API → result |

## Flutter-Specific Patterns

### Pattern 1: Widget Test with Real Providers

```dart
testWidgets('favorite button toggles and persists', (tester) async {
  final fakePrefs = FakeSharedPreferences();
  
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        sharedPreferencesProvider.overrideWithValue(fakePrefs),
      ],
      child: MaterialApp(home: StopDetailPage(stop: testStop)),
    ),
  );
  
  // Verify initial state: not favorited
  expect(find.byIcon(Icons.favorite_border), findsOneWidget);
  
  // Act: tap favorite
  await tester.tap(find.byIcon(Icons.favorite_border));
  await tester.pumpAndSettle();
  
  // Assert: icon changed AND data persisted
  expect(find.byIcon(Icons.favorite), findsOneWidget);
  expect(fakePrefs.getStringList('favorites'), contains(testStop.id));
});
```

**Bug this catches:** UI updates but data doesn't persist (or vice versa).

### Pattern 2: Navigation Integration Test

```dart
testWidgets('tapping map link navigates to map page', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      child: MaterialApp.router(routerConfig: appRouter),
    ),
  );

  // Navigate to AI assistant
  await tester.tap(find.text('AI 助手'));
  await tester.pumpAndSettle();

  // Simulate map link tap
  // Find the MarkdownBody and trigger onTapLink
  final markdownFinder = find.byType(MarkdownBody);
  // ... trigger link callback

  await tester.pumpAndSettle();

  // Assert: now on map page
  expect(find.byType(MapPage), findsOneWidget);
});
```

### Pattern 3: Async Provider Integration

```dart
testWidgets('loading → data → display flow', (tester) async {
  final fakeRepo = FakeVehicleRepository();
  
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        vehicleRepoProvider.overrideWithValue(fakeRepo),
      ],
      child: MaterialApp(home: MapPage()),
    ),
  );

  // Initially shows loading
  expect(find.byType(CircularProgressIndicator), findsOneWidget);
  
  // Simulate data arrival
  fakeRepo.emitVehicles(testVehicles);
  await tester.pumpAndSettle();
  
  // Shows data
  expect(find.byType(CircularProgressIndicator), findsNothing);
  expect(find.text('車號 ABC-123'), findsOneWidget);
});
```

### Pattern 4: Error State Integration

```dart
testWidgets('API failure shows error UI with retry', (tester) async {
  final fakeRepo = FakeVehicleRepository();
  
  await tester.pumpWidget(testApp(fakeRepo));
  
  // Simulate error
  fakeRepo.emitError(Exception('Server 500'));
  await tester.pumpAndSettle();
  
  // Shows error message (not raw exception)
  expect(find.text('Server 500'), findsNothing);  // No raw error
  expect(find.textContaining('暫時無法'), findsOneWidget);
  
  // Retry button works
  fakeRepo.willSucceedNext = true;
  await tester.tap(find.text('重試'));
  await tester.pumpAndSettle();
  
  expect(find.byType(CircularProgressIndicator), findsNothing);
  expect(find.textContaining('資料已更新'), findsOneWidget);
});
```

### Pattern 5: E2E CRUD Flow

```dart
testWidgets('full CRUD: add → view → edit → delete favorite', (tester) async {
  await tester.pumpWidget(testApp());
  
  // CREATE
  await tester.tap(find.byIcon(Icons.favorite_border).first);
  await tester.pumpAndSettle();
  expect(find.byIcon(Icons.favorite), findsOneWidget);
  
  // READ - navigate to favorites
  await tester.tap(find.text('設定'));
  await tester.pumpAndSettle();
  await tester.tap(find.text('我的收藏'));
  await tester.pumpAndSettle();
  expect(find.text(testStop.name), findsOneWidget);
  
  // DELETE
  await tester.tap(find.byIcon(Icons.delete).first);
  await tester.pumpAndSettle();
  expect(find.text(testStop.name), findsNothing);
});
```

## Integration Test Structure

```dart
// test/e2e/feature_name_test.dart

import 'package:flutter_test/flutter_test.dart';

// Helper to create test app with all required overrides
Widget testApp({List? extraOverrides}) {
  return ProviderScope(
    overrides: [
      // Always override external dependencies
      sharedPreferencesProvider.overrideWithValue(FakeSharedPreferences()),
      locationServiceProvider.overrideWithValue(FakeLocationService()),
      ...?extraOverrides,
    ],
    child: MaterialApp.router(
      routerConfig: appRouter,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
    ),
  );
}
```

## What to Test in Integration (Bug-Finding Priority)

1. **Cross-widget data flow** — Provider state → Widget display
2. **Navigation with parameters** — Route params arrive correctly
3. **User interaction sequences** — Multi-step flows that exercise real paths
4. **Error recovery** — Error state → retry → success
5. **State persistence** — Data survives page navigation
6. **Responsive layout** — Key UI works at different screen sizes

## Anti-Patterns to AVOID

### ❌ Over-Mocking in Integration Tests
```dart
// BAD: Everything is mocked — you're testing nothing real
testWidgets('page loads', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        dataProvider.overrideWithValue(AsyncData(mockData)),
        // 10 more overrides...
      ],
      child: MockPage(), // Even the page is mocked!
    ),
  );
  expect(find.text('mock'), findsOneWidget); // Tests mock, not app
});
```

### ❌ No `pumpAndSettle` After State Change
```dart
// BAD: Assertions before widget tree rebuilds
await tester.tap(find.byIcon(Icons.add));
// Missing: await tester.pumpAndSettle();
expect(find.text('Added'), findsOneWidget); // FLAKY!
```

### ❌ Testing Widget Existence Without Behavior
```dart
// BAD: Only checks widget tree, not behavior
expect(find.byType(ElevatedButton), findsOneWidget);
// So what? Does the button DO anything?

// GOOD: Test the button works
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(someStateChanged, isTrue);
```

## Localization in Tests

Always include localization delegates for widget tests that render localized text:

```dart
MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  home: PageUnderTest(),
)
```

## Verification Checklist

- [ ] Tests exercise real user flows (not just render checks)
- [ ] External deps (network, DB, GPS) are faked, not the components under test
- [ ] Error states tested (not just happy path)
- [ ] Navigation tested with parameter passing
- [ ] Async loading states verified (loading → data → error)
- [ ] No flaky time-dependent assertions
- [ ] All tests pass: `fvm flutter test`

## Related skills

- **`integration-testing-dart`** — **DISAMBIGUATION**: integration-testing-dart covers multi-module Dart integration test patterns. flutter-integration-testing is Flutter-specific widget integration testing. Use flutter-integration-testing for Flutter widget tests; use integration-testing-dart for Dart module integration tests.
- **`test-driven-development`** — use TDD for integration test implementation.
- **`testing-anti-patterns`** — audit for mocking errors after integration tests pass.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ImL1s](https://github.com/ImL1s)
- **Source:** [ImL1s/flutter-claude-skills](https://github.com/ImL1s/flutter-claude-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-iml1s-flutter-claude-skills-flutter-integration-testing
- Seller: https://agentstack.voostack.com/s/iml1s
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
