문서 목차
이 페이지에서목차

flutter-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.

검색어를 입력하면 문서를 찾습니다.

↑ ↓ 이동 · Enter 열기Esc 닫기