flutter-testing

Flutter testing: Widget Tests (testWidgets, WidgetTester, pump, pumpAndSettle, finders), Integration Tests (flutter_test driver), mocking with mocktail, Golden File Tests (visual regression, --update-goldens), BLoC testing (bloc_test package), coverage measurement and CI integration.

8 stars

Best use case

flutter-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Flutter testing: Widget Tests (testWidgets, WidgetTester, pump, pumpAndSettle, finders), Integration Tests (flutter_test driver), mocking with mocktail, Golden File Tests (visual regression, --update-goldens), BLoC testing (bloc_test package), coverage measurement and CI integration.

Teams using flutter-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/flutter-testing/SKILL.md --create-dirs "https://raw.githubusercontent.com/marvinrichter/clarc/main/skills/flutter-testing/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/flutter-testing/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How flutter-testing Compares

Feature / Agentflutter-testingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Flutter testing: Widget Tests (testWidgets, WidgetTester, pump, pumpAndSettle, finders), Integration Tests (flutter_test driver), mocking with mocktail, Golden File Tests (visual regression, --update-goldens), BLoC testing (bloc_test package), coverage measurement and CI integration.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Flutter Testing Skill

Flutter provides three test levels built into the framework. This skill covers the patterns for each, from unit-testing pure Dart logic to visual regression golden tests.

## When to Activate

- Writing tests for a new Flutter widget or feature
- Setting up visual regression testing with golden files
- Testing BLoC state machines
- Debugging flaky widget tests
- Setting up CI test pipeline for Flutter
- Choosing the right test level (unit, widget, or integration) for a given piece of logic
- Mocking dependencies with mocktail when code generation from mockito is not desirable
- Adding code coverage gating to a Flutter CI pipeline with lcov and Codecov

---

## Test Pyramid

```
┌──────────────────────────┐
│   Integration Tests       │  Few — slow, real device/emulator
│   (flutter_test driver)   │
├──────────────────────────┤
│   Widget Tests            │  Many — fast, no device needed
│   (testWidgets)           │
├──────────────────────────┤
│   Unit Tests              │  Most — pure Dart, fastest
│   (test package)          │
└──────────────────────────┘
```

---

## Unit Tests

Pure Dart — no widgets, no Flutter framework.

```dart
// test/unit/domain/cart_service_test.dart
import 'package:test/test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:myapp/domain/services/cart_service.dart';

class MockCartRepository extends Mock implements CartRepository {}

void main() {
  group('CartService.addItem', () {
    late CartService service;
    late MockCartRepository mockRepo;

    setUp(() {
      mockRepo = MockCartRepository();
      service = CartService(repository: mockRepo);
      registerFallbackValue(CartItem.empty());
    });

    test('adds item to cart', () async {
      when(() => mockRepo.save(any())).thenAnswer((_) async {});
      when(() => mockRepo.load()).thenAnswer((_) async => []);

      final result = await service.addItem(Product(id: 'p1', price: 10.0));

      expect(result, hasLength(1));
      expect(result.first.productId, 'p1');
      verify(() => mockRepo.save(any())).called(1);
    });

    test('throws CartFullException when cart has 100 items', () async {
      when(() => mockRepo.load()).thenAnswer(
        (_) async => List.generate(100, (_) => CartItem.empty()),
      );

      expect(
        () => service.addItem(Product(id: 'p1', price: 10.0)),
        throwsA(isA<CartFullException>()),
      );
    });
  });
}
```

---

## Widget Tests

```dart
// test/widget/features/product/product_card_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('ProductCard', () {
    testWidgets('displays product name and price', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: ProductCard(
              product: Product(id: 'p1', name: 'Widget Pro', price: 49.99),
            ),
          ),
        ),
      );

      expect(find.text('Widget Pro'), findsOneWidget);
      expect(find.text('\$49.99'), findsOneWidget);
    });

    testWidgets('calls onAddToCart when button tapped', (tester) async {
      var tapped = false;

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: ProductCard(
              product: Product(id: 'p1', name: 'Test', price: 9.99),
              onAddToCart: () => tapped = true,
            ),
          ),
        ),
      );

      await tester.tap(find.byKey(const Key('add_to_cart_button')));
      await tester.pump();

      expect(tapped, isTrue);
    });

    testWidgets('shows loading state during async operation', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(home: CheckoutScreen()),
      );

      await tester.tap(find.text('Place Order'));
      await tester.pump();  // Start the async operation

      expect(find.byType(CircularProgressIndicator), findsOneWidget);

      await tester.pumpAndSettle();  // Wait for async completion

      expect(find.text('Order Confirmed'), findsOneWidget);
    });
  });
}
```

### Key Finders

```dart
find.byType(ProductCard)              // by widget type
find.byKey(const Key('submit'))        // by key (most stable)
find.text('Add to Cart')               // by exact text
find.textContaining('Cart')            // partial text
find.byIcon(Icons.shopping_cart)       // by icon
find.descendant(of: find.byType(Card), matching: find.byType(Text))
find.ancestor(of: find.text('price'), matching: find.byType(Card))
```

### pump vs. pumpAndSettle

```dart
await tester.pump();                            // one frame
await tester.pump(const Duration(seconds: 1)); // simulate time
await tester.pumpAndSettle();                   // wait for all frames (animations, futures)
await tester.pumpAndSettle(const Duration(seconds: 5));  // with timeout
```

---

## mocktail

Preferred over mockito — no code generation required.

```dart
import 'package:mocktail/mocktail.dart';

// Create mock
class MockUserRepository extends Mock implements UserRepository {}

// Setup
setUp(() {
  mock = MockUserRepository();
  // Required for non-nullable types used as arguments
  registerFallbackValue(User.empty());
});

// Stub
when(() => mock.findById('1')).thenReturn(User(id: '1', name: 'Alice'));
when(() => mock.findById('missing')).thenThrow(NotFoundException('1'));
when(() => mock.save(any())).thenAnswer((_) async {});

// Async stubs
when(() => mock.fetchAll())
  .thenAnswer((_) async => [User(id: '1', name: 'Alice')]);

// Verify
verify(() => mock.findById('1')).called(1);
verifyNever(() => mock.delete(any()));
```

---

## BLoC Testing (bloc_test)

```dart
import 'package:bloc_test/bloc_test.dart';

void main() {
  group('CartBloc', () {
    late MockCartRepository repo;

    setUp(() => repo = MockCartRepository());

    blocTest<CartBloc, CartState>(
      'emits [loading, withItem] when AddToCart succeeds',
      build: () => CartBloc(repository: repo),
      setUp: () => when(() => repo.addItem(any(), any()))
        .thenAnswer((_) async => [CartItem(productId: 'p1')]),
      act: (bloc) => bloc.add(AddToCart(product: Product(id: 'p1'))),
      expect: () => [
        const CartState(isLoading: true),
        CartState(items: [CartItem(productId: 'p1')]),
      ],
    );

    blocTest<CartBloc, CartState>(
      'emits [loading, error] when repository throws',
      build: () => CartBloc(repository: repo),
      setUp: () => when(() => repo.addItem(any(), any()))
        .thenThrow(CartException('Out of stock')),
      act: (bloc) => bloc.add(AddToCart(product: Product(id: 'p1'))),
      expect: () => [
        const CartState(isLoading: true),
        const CartState(error: CartError('Out of stock')),
      ],
    );

    blocTest<CartBloc, CartState>(
      'emits nothing when same item added twice rapidly',
      build: () => CartBloc(repository: repo),
      act: (bloc) => bloc
        ..add(AddToCart(product: Product(id: 'p1')))
        ..add(AddToCart(product: Product(id: 'p1'))),
      expect: () => [/* whatever your deduplication logic produces */],
    );
  });
}
```

---

## Golden File Tests (Visual Regression)

```dart
// test/golden/product_card_golden_test.dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('ProductCard - default state', (tester) async {
    await tester.binding.setSurfaceSize(const Size(400, 200));

    await tester.pumpWidget(
      MaterialApp(
        theme: AppTheme.light,
        home: Scaffold(
          body: ProductCard(product: Product.sample()),
        ),
      ),
    );

    await expectLater(
      find.byType(ProductCard),
      matchesGoldenFile('goldens/product_card_default.png'),
    );
  });
}
```

```bash
# Create/update baseline images
flutter test --update-goldens test/golden/

# Run comparison (fail on visual diff)
flutter test test/golden/

# CI: always run comparison, never auto-update
```

**Golden test best practices:**
- Use fixed `Surface Size` for consistent output across machines
- Use deterministic data (no timestamps, random values)
- Commit golden files to git — they're your visual baseline
- Group golden tests separately from functional tests (different run frequency)

---

## Coverage

```bash
# Run tests with coverage
flutter test --coverage

# Coverage report in: coverage/lcov.info
# View HTML report
genhtml coverage/lcov.info --output-directory coverage/html
open coverage/html/index.html

# CI gate
lcov --summary coverage/lcov.info
# Check "lines......" percentage
```

```yaml
# .github/workflows/flutter.yml
- name: Test with coverage
  run: flutter test --coverage

- name: Upload to Codecov
  uses: codecov/codecov-action@v3
  with:
    file: coverage/lcov.info
```

---

## Testability Patterns

Make code testable from the start:

```dart
// WRONG: hardwired dependency
class OrderService {
  final repo = OrderRepository();  // can't mock

// CORRECT: constructor injection
class OrderService {
  const OrderService(this._repository);
  final OrderRepository _repository;
}

// CORRECT: abstract interface for mocking
abstract class OrderRepository {
  Future<Order> create(OrderRequest request);
}

class RemoteOrderRepository implements OrderRepository { /* ... */ }
class MockOrderRepository extends Mock implements OrderRepository {}
```

---

## Reference

- `rules/flutter/testing.md` — test conventions, tooling setup
- `skills/flutter-patterns` — BLoC/Riverpod architecture patterns
- `skills/dart-patterns` — sealed classes, Result pattern for error handling

Related Skills

visual-testing

8
from marvinrichter/clarc

Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.

typescript-testing

8
from marvinrichter/clarc

TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.

swift-testing

8
from marvinrichter/clarc

Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.

swift-protocol-di-testing

8
from marvinrichter/clarc

Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.

scala-testing

8
from marvinrichter/clarc

Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.

rust-testing

8
from marvinrichter/clarc

Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.

rust-testing-advanced

8
from marvinrichter/clarc

Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with

ruby-testing

8
from marvinrichter/clarc

RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.

r-testing

8
from marvinrichter/clarc

R testing patterns: testthat 3e with expect_* assertions, snapshot testing, mocking with mockery and httptest2, covr code coverage, lintr static analysis, property-based testing with hedgehog, testing Shiny apps with shinytest2. Use when writing or reviewing R tests.

python-testing

8
from marvinrichter/clarc

Python testing strategies using pytest, TDD methodology, fixtures, mocking, and parametrization. Core testing fundamentals.

python-testing-advanced

8
from marvinrichter/clarc

Advanced Python testing — async testing with pytest-asyncio, exception/side-effect testing, test organization, common patterns (API, database, class methods), pytest configuration, and CLI reference. Extends python-testing.

php-testing

8
from marvinrichter/clarc

PHP testing patterns: PHPUnit 11 with mocks and data providers, Pest v3 with expectations and datasets, Laravel feature/HTTP tests with RefreshDatabase, Symfony WebTestCase, PHPStan static analysis, Infection mutation testing. Use when writing or reviewing PHP tests.