5.0 KiB
5.0 KiB
Test Utilities Guide
본 문서는 Flutter 위젯/서비스 테스트를 안정적으로 작성하기 위한 공용 유틸 사용 가이드를 제공합니다. 실제 예시는 프로젝트 내 테스트 코드에서 그대로 복사해 사용할 수 있습니다.
구성 요소
- test/utils/mock_services.dart
- test/utils/stub_event_service.dart
- test/utils/provider_presets.dart
- test/utils/widget_test_utils.dart (향후 확장 시)
- test/utils/event_bus_test_utils.dart
공통 패턴
- 오버레이/네비 의존 제거: 팝업/스낵바/네비게이션은 테스트에서
pumpAndSettle를 가로막는 대표 원인입니다. 가능하면 화면에@visibleForTesting훅(예:invokeSaveForTest,invokeDuplicateForTest)을 제공하여 내부 로직을 직접 호출하세요. - pumpAndSettle 지양: 무한 대기 가능성이 있으므로,
await tester.pump()+await tester.pump(const Duration(milliseconds: 50))조합을 우선적으로 사용합니다. - ensureVisible: 탭/토글/입력 전에는
await tester.ensureVisible(finder)로 화면 내 가시성을 보장합니다. - Key 부여: 테스트가 찾는 주요 위젯에는
ValueKey를 부여합니다. 예)ValueKey('access_password_field'),ValueKey('event_menu_<id>'). - SharedPreferences 모킹: 영속 값(검색어 등)을 사용 시
SharedPreferences.setMockInitialValues({...})를 통해 초기 상태를 정의합니다.
Mock/Stub 소개
MockAuthService / MockClubService (test/utils/mock_services.dart)
- MockAuthService
- 고정 토큰과 사용자 정보(
User)를 제공합니다. isAuthenticated,currentUser,token등을 간단히 반환합니다.
- 고정 토큰과 사용자 정보(
- MockClubService
- 단일
Club을 보유하고currentClub/clubs를 제공합니다. updateClub()는ownerId반영 정도만 최소 구현했습니다.
- 단일
사용 예시:
final auth = MockAuthService(
user: User(id: 'u1', email: 'u@ex.com', name: 'Tester', role: 'owner'),
tokenValue: 'token',
);
final club = MockClubService(
club: Club(id: 'club1', name: '클럽', averageCalculationPeriod: '3'),
);
StubEventService (test/utils/stub_event_service.dart)
- 네트워크 호출을 모두 차단하고 즉시 완료되는
EventService대체 구현입니다. - 관찰 필드
lastCloneCalled,lastClonedEventId,lastCreatedEventData
- 시드 이벤트
seededEvents에Event리스트를 주입해 목록을 렌더링합니다.
- 안정화 포인트
createEvent/cloneEvent는 notifyListeners 후 바로 반환합니다.
사용 예시:
final stub = StubEventService();
await stub.initialize('token');
stub.setClubId('club1');
stub.seededEvents = [Event(id: 'e1', clubId: 'club1', title: '타이틀', startDate: DateTime.now(), status: '활성', isActive: true)];
Provider 프리셋 (test/utils/provider_presets.dart)
- 특정 화면에 필요한 Provider 집합을 손쉽게 구성합니다.
- 현재 예시는 ClubSettings 시나리오용으로 제공되며, 필요 시 확장하세요.
사용 예시:
MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>.value(value: auth),
ChangeNotifierProvider<ClubService>.value(value: club),
ChangeNotifierProvider<EventService>.value(value: stub),
],
child: const MaterialApp(home: EventsScreen()),
);
화면별 테스트 팁
EventsScreen
- 복제 메뉴 테스트:
PopupMenuButton오버레이를 열지 말고, 상태 훅invokeDuplicateForTest(event)를 호출하세요. - ellipsis 회귀 테스트:
Text위젯을find.textContaining()으로 찾고,Text.maxLines/Text.overflow를 직접 검증하세요.
EventFormScreen
- 저장 테스트: 저장 버튼 탭 대신
invokeSaveForTest()호출. 테스트 모드에서는 pop/스낵바를 우회하고_isLoading을 해제하는 로직이 포함되어 있어 hang 방지에 유리합니다. - 비밀번호 필드:
ValueKey('access_password_field')로 필드 접근, 토글 아이콘은Tooltip('비밀번호 표시')/('비밀번호 숨김')으로 찾습니다.
EventBus 테스트 유틸 (test/utils/event_bus_test_utils.dart)
- 각 테스트에서 고유 테스트 ID를 설정하고, setUp/tearDown에서
EventBus를 초기화/해제합니다. - 간헐적 경합을 줄이기 위해 tearDown에서 짧은 대기(
Future.delayed)를 추가합니다.
샘플 패턴:
setUp(() async {
final testId = 'member_service_test_${DateTime.now().millisecondsSinceEpoch}';
await EventBusTestUtils.setUp(testId);
});
tearDown(() async {
await EventBusTestUtils.tearDown();
await Future<void>.delayed(const Duration(milliseconds: 100));
});
권장 사항 요약
- 핵심: 오버레이/네비 의존 제거, 테스트 훅 사용,
pumpAndSettle지양. - 상태:
_isLoading은 성공/실패 모두 해제되도록 보장. - 가시성: 액션 전
ensureVisible사용. - 모델/타입: 실제 모델 시그니처에 맞는 파라미터(문자열/정수, const 사용 여부 등) 확인.