LogoSkills

flutter-testing

Add Flutter unit tests, widget tests, or integration tests. Use when writing or structuring any Flutter test layer.

flutter-automated-testing#

ํ•œ๋งˆ๋””๋กœ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

์ด ์Šคํ‚ฌ์€ Flutter ์•ฑ์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ์ž๋™์œผ๋กœ ์ ๊ฒ€ํ•˜๋Š” "๊ฒ€์‚ฌ ๋กœ๋ด‡"์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ์‚ฌ๋žŒ์ด ๋งค๋ฒˆ ์†์œผ๋กœ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ๋ณด๋ฉฐ ํ™•์ธํ•˜๋Š” ๋Œ€์‹ , ์ฝ”๋“œ๋กœ ์งœ์ธ ๊ฒ€์‚ฌ ํ•ญ๋ชฉ์ด ์•Œ์•„์„œ ์•ฑ์„ ๋‘๋“œ๋ ค ๋ณด๊ณ  ๋ฌธ์ œ๋ฅผ ์žก์•„๋ƒ…๋‹ˆ๋‹ค. ๋งˆ์น˜ ์ž๋™์ฐจ๊ฐ€ ์ถœ๊ณ ๋˜๊ธฐ ์ „ ๊ณต์žฅ์—์„œ ๊ฑฐ์น˜๋Š” ํ’ˆ์งˆ ๊ฒ€์‚ฌ ๋ผ์ธ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ฌด์—‡์„ยท์–ธ์ œ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

  • ๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”: ์•ฑ์˜ ๊ธฐ๋Šฅ์ด ์˜๋„๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ž๋™ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ์„ค์ •ํ•˜๊ณ , ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ๊ณ ์ณ ์ค๋‹ˆ๋‹ค.
  • ๊ฒ€์‚ฌ ์ข…๋ฅ˜๋Š” ํฌ๊ฒŒ ๋„ค ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.
    • ๋ถ€ํ’ˆ ๊ฒ€์‚ฌ(Unit Test): ๊ณ„์‚ฐ ๋กœ์ง์ด๋‚˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ฐ™์€ ์ž‘์€ ๋‹จ์œ„๊ฐ€ ๋งž๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธ.
    • ํ™”๋ฉด ๊ฒ€์‚ฌ(Widget Test): ๋ฒ„ํŠผ, ๊ธ€์ž ๋“ฑ ํ™”๋ฉด ์š”์†Œ๊ฐ€ ์ž˜ ๋ณด์ด๊ณ  ๋ˆŒ๋Ÿฌ์ง€๋ฉด ๋ฐ˜์‘ํ•˜๋Š”์ง€ ํ™•์ธ.
    • ์ „์ฒด ํ๋ฆ„ ๊ฒ€์‚ฌ(Integration Test): ์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ์•ฑ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์‚ฌ์šฉํ•ด ๋ณด๋ฉฐ ํ™•์ธ.
    • ๋„ค์ดํ‹ฐ๋ธŒ ์—ฐ๋™ ๊ฒ€์‚ฌ(Plugin Test): ์•ˆ๋“œ๋กœ์ด๋“œ/iOS์˜ ๊ธฐ๊ธฐ ๊ณ ์œ  ๊ธฐ๋Šฅ๊ณผ ์ž˜ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธ.
  • ์–ธ์ œ ์ž‘๋™ํ•˜๋‚˜์š”: Flutter ์•ฑ์— ์ƒˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๊ธฐ์กด ํ…Œ์ŠคํŠธ๋ฅผ ์ •๋ฆฌยท๊ตฌ์กฐํ™”ํ•˜๊ฑฐ๋‚˜, ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ด์„œ ์›์ธ์„ ์ฐพ์•„ ๊ณ ์ณ์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์šฉ์–ด (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

์šฉ์–ด์‰ฌ์šด ์„ค๋ช…
Unit Testํ•จ์ˆ˜๋‚˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ฐ™์€ ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„๊ฐ€ ๋งž๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ณด๋Š” ๋ถ€ํ’ˆ ๊ฒ€์‚ฌ
Widget Test๋ฒ„ํŠผยท๊ธ€์ž ๋“ฑ ํ™”๋ฉด ์š”์†Œ๊ฐ€ ์ž˜ ๋ณด์ด๊ณ  ๋ฐ˜์‘ํ•˜๋Š”์ง€ ๋ณด๋Š” ํ™”๋ฉด ๊ฒ€์‚ฌ
Integration Test์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ์•ฑ ์ „์ฒด ํ๋ฆ„์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋Œ๋ ค ๋ณด๋Š” ์ข…ํ•ฉ ๊ฒ€์‚ฌ
Plugin Test์•ˆ๋“œ๋กœ์ด๋“œยทiOS์˜ ๊ธฐ๊ธฐ ๊ณ ์œ  ๊ธฐ๋Šฅ๊ณผ ์ž˜ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ๋ณด๋Š” ์—ฐ๋™ ๊ฒ€์‚ฌ
Fake / Mock์ง„์งœ ์™ธ๋ถ€ ์‹œ์Šคํ…œ(์„œ๋ฒ„, DB) ๋Œ€์‹  ์“ฐ๋Š” ๊ฐ€์งœ ๋Œ€์—ญ โ€” ๊ฒ€์‚ฌ ํ™˜๊ฒฝ์„ ๋‹จ์ˆœํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์คŒ
ViewModelํ™”๋ฉด ๋’ค์—์„œ ๋ฐ์ดํ„ฐ์™€ ๋™์ž‘์„ ์ฒ˜๋ฆฌํ•˜๋Š” "๋‘๋‡Œ" ์—ญํ• ์˜ ์ฝ”๋“œ
Repository๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ €์žฅํ•˜๋Š” ์ผ์„ ๋งก์€ ์ฐฝ๊ณ  ๊ด€๋ฆฌ์ž ๊ฐ™์€ ์ฝ”๋“œ
pump / pumpAndSettleํ™”๋ฉด์„ ํ•œ ๋‹จ๊ณ„ ๊ฐฑ์‹ ํ•˜๊ฑฐ๋‚˜(์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚  ๋•Œ๊นŒ์ง€) ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๋Š” ๋™์ž‘

Goal#

Generates, configures, and debugs automated tests for Flutter applications, encompassing unit, widget, integration, and plugin testing. Analyzes architectural components (such as MVVM layers) to produce isolated, mock-driven tests and end-to-end device tests. Assumes a standard Flutter project structure, existing business logic, and familiarity with Dart testing paradigms.

Instructions#

1. Determine Test Type (Decision Logic)#

Evaluate the user's target code to determine the appropriate testing strategy using the following decision tree:

  • If verifying a single function, method, ViewModel, or Repository: Implement a Unit Test (Proceed to Step 2).
  • If verifying a single widget's UI, layout, or interaction: Implement a Widget Test (Proceed to Step 3).
  • If verifying complete app behavior, routing, or performance on a device: Implement an Integration Test (Proceed to Step 4).
  • If verifying platform-specific native code (MethodChannels): Implement a Plugin Test (Proceed to Step 5).

STOP AND ASK THE USER: "Which specific class, widget, or flow are we testing today? Please provide the relevant source code if you haven't already."

2. Implement Unit Tests (Logic & Architecture)#

Unit tests verify logic without rendering UI. They must reside in the test/ directory and end with _test.dart.

  • For ViewModels (UI Layer Logic): Fake the repository dependencies. Do not rely on Flutter UI libraries.
import 'package:test/test.dart';
// Import your ViewModel and Fakes here

void main() {
  group('HomeViewModel tests', () {
    test('Load bookings successfully', () {
      final viewModel = HomeViewModel(
        bookingRepository: FakeBookingRepository()..createBooking(kBooking),
        userRepository: FakeUserRepository(),
      );

      expect(viewModel.bookings.isNotEmpty, true);
    });
  });
}
  • For Repositories (Data Layer Logic): Fake the API clients or local database services.
import 'package:test/test.dart';
// Import your Repository and Fakes here

void main() {
  group('BookingRepositoryRemote tests', () {
    late BookingRepository bookingRepository;
    late FakeApiClient fakeApiClient;

    setUp(() {
      fakeApiClient = FakeApiClient();
      bookingRepository = BookingRepositoryRemote(apiClient: fakeApiClient);
    });

    test('should get booking', () async {
      final result = await bookingRepository.getBooking(0);
      final booking = result.asOk.value;
      expect(booking, kBooking);
    });
  });
}

3. Implement Widget Tests (UI Components)#

Widget tests verify UI rendering and interaction. They must reside in the test/ directory and use the flutter_test package.

  • Use WidgetTester to build the widget.
  • Use Finder to locate elements (find.text(), find.byKey(), find.byWidget()).
  • Use Matcher to verify existence (findsOneWidget, findsNothing, findsNWidgets).
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('HomeScreen displays title and handles tap', (WidgetTester tester) async {
    // 1. Setup Fakes and ViewModel
    final bookingRepository = FakeBookingRepository()..createBooking(kBooking);
    final viewModel = HomeViewModel(
      bookingRepository: bookingRepository,
      userRepository: FakeUserRepository(),
    );

    // 2. Build the Widget tree
    await tester.pumpWidget(
      MaterialApp(
        home: HomeScreen(viewModel: viewModel),
      ),
    );

    // 3. Finders
    final titleFinder = find.text('Home');
    final buttonFinder = find.byKey(const Key('increment_button'));

    // 4. Assertions
    expect(titleFinder, findsOneWidget);

    // 5. Interactions
    await tester.tap(buttonFinder);
    await tester.pumpAndSettle(); // Wait for animations/state updates to finish

    expect(find.text('1'), findsOneWidget);
  });
}

4. Implement Integration Tests (End-to-End)#

Integration tests run on real devices or emulators. They must reside in the integration_test/ directory.

  • Ensure integration_test is in dev_dependencies in pubspec.yaml.
  • Initialize IntegrationTestWidgetsFlutterBinding.
import 'package:flutter/material.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();

  group('End-to-End App Test', () {
    testWidgets('Full flow: tap FAB and verify counter', (WidgetTester tester) async {
      // Load the full app
      app.main();
      await tester.pumpAndSettle();

      // Verify initial state
      expect(find.text('0'), findsOneWidget);

      // Find and tap the FAB
      final fab = find.byKey(const ValueKey('increment'));
      await tester.tap(fab);
      
      // Trigger a frame
      await tester.pumpAndSettle();

      // Verify state change
      expect(find.text('1'), findsOneWidget);
    });
  });
}

5. Implement Plugin Tests (Native & Dart)#

If testing a plugin, tests must cover both Dart and Native communication.

  • Dart Side: Mock the platform channel and call the plugin's public API.
  • Native Side: Instruct the user to write native tests in the respective directories:
    • Android: android/src/test/ (JUnit)
    • iOS/macOS: example/ios/RunnerTests/ (XCTest)
    • Linux/Windows: linux/test/ (GoogleTest)

6. Validate and Fix (Feedback Loop)#

Provide the user with the exact command to run the generated test:

  • Unit/Widget: flutter test test/your_test_file.dart
  • Integration: flutter test integration_test/your_test_file.dart

STOP AND ASK THE USER: "Please run the test using the command above and paste the output. If the test fails, provide the stack trace so I can analyze the failure and generate a fix."

Constraints#

  • Single Source of Truth: Do not duplicate state in tests. Always use fakes or mocks for external dependencies (Repositories, Services) to isolate the unit under test.
  • No Logic in Widgets: When writing widget tests, assume the widget is "dumb". All business logic should be tested via the ViewModel/Controller unit tests.
  • File Naming: All test files MUST end with _test.dart.
  • Pump vs PumpAndSettle: Use tester.pump() for single frame advances. Use tester.pumpAndSettle() strictly when waiting for animations or asynchronous UI updates to complete.
  • Immutability: Treat test data models as immutable. Create new instances for state changes rather than mutating existing mock data.
  • Do not use dart:mirrors: Flutter does not support reflection. Rely on code generation (e.g., build_runner, mockito, mocktail) for mocking.