Files
superport/test/helpers/test_helpers.dart
JiWoong Sul d6f34c0a52 test: 테스트 자동화 구현 및 Mock 서비스 오류 수정
- 테스트 패키지 추가 (mockito, golden_toolkit, patrol 등)
- 테스트 가이드 문서 작성 (TEST_GUIDE.md)
- 테스트 진행 상황 문서 작성 (TEST_PROGRESS.md)
- 테스트 헬퍼 클래스 구현
  - test_helpers.dart: 기본 테스트 유틸리티
  - mock_data_helpers.dart: Mock 데이터 생성 헬퍼
  - mock_services.dart: Mock 서비스 설정 (오류 수정 완료)
  - simple_mock_services.dart: 간단한 Mock 서비스
- 단위 테스트 구현
  - CompanyListController 테스트
  - EquipmentListController 테스트
  - UserListController 테스트
- Widget 테스트 구현 (CompanyListScreen)

Mock 서비스 주요 수정사항:
- dartz import 추가
- Either 타입 제거 (실제 서비스와 일치하도록)
- 메서드 시그니처 수정 (실제 서비스 인터페이스와 일치)
- Mock 데이터 생성 메서드 추가

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-31 20:37:26 +09:00

296 lines
7.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
/// 테스트용 GetIt 인스턴스 초기화
GetIt setupTestGetIt() {
final getIt = GetIt.instance;
// 기존 등록된 서비스들 모두 제거
getIt.reset();
return getIt;
}
/// 테스트용 위젯 래퍼
/// 모든 위젯 테스트에서 필요한 기본 설정을 제공
class TestWidgetWrapper extends StatelessWidget {
final Widget child;
final List<ChangeNotifierProvider>? providers;
final NavigatorObserver? navigatorObserver;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
const TestWidgetWrapper({
Key? key,
required this.child,
this.providers,
this.navigatorObserver,
this.routes,
this.initialRoute,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget wrappedChild = MaterialApp(
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('ko', 'KR'),
Locale('en', 'US'),
],
home: Scaffold(body: child),
routes: routes ?? {},
initialRoute: initialRoute,
navigatorObservers: navigatorObserver != null ? [navigatorObserver!] : [],
);
// Provider가 있는 경우 래핑
if (providers != null && providers!.isNotEmpty) {
return MultiProvider(
providers: providers!,
child: wrappedChild,
);
}
return wrappedChild;
}
}
/// 위젯을 테스트 환경에서 펌프하는 헬퍼 함수
Future<void> pumpTestWidget(
WidgetTester tester,
Widget widget, {
List<ChangeNotifierProvider>? providers,
NavigatorObserver? navigatorObserver,
Map<String, WidgetBuilder>? routes,
String? initialRoute,
}) async {
await tester.pumpWidget(
TestWidgetWrapper(
child: widget,
providers: providers,
navigatorObserver: navigatorObserver,
routes: routes,
initialRoute: initialRoute,
),
);
}
/// 비동기 작업을 기다리고 위젯을 리빌드하는 헬퍼
Future<void> pumpAndSettleWithTimeout(
WidgetTester tester, {
Duration timeout = const Duration(seconds: 10),
}) async {
await tester.pump();
await tester.pumpAndSettle(timeout);
}
/// TextField에 텍스트를 입력하는 헬퍼
Future<void> enterTextByLabel(
WidgetTester tester,
String label,
String text,
) async {
final textFieldFinder = find.ancestor(
of: find.text(label),
matching: find.byType(TextFormField),
);
if (textFieldFinder.evaluate().isEmpty) {
// 라벨로 찾지 못한 경우, 가까운 TextFormField 찾기
final labelWidget = find.text(label);
final textField = find.byType(TextFormField).first;
await tester.enterText(textField, text);
} else {
await tester.enterText(textFieldFinder, text);
}
}
/// 버튼을 찾고 탭하는 헬퍼
Future<void> tapButtonByText(
WidgetTester tester,
String buttonText,
) async {
final buttonFinder = find.widgetWithText(ElevatedButton, buttonText);
if (buttonFinder.evaluate().isEmpty) {
// ElevatedButton이 아닌 경우 다른 버튼 타입 시도
final textButtonFinder = find.widgetWithText(TextButton, buttonText);
if (textButtonFinder.evaluate().isNotEmpty) {
await tester.tap(textButtonFinder);
return;
}
final outlinedButtonFinder = find.widgetWithText(OutlinedButton, buttonText);
if (outlinedButtonFinder.evaluate().isNotEmpty) {
await tester.tap(outlinedButtonFinder);
return;
}
// 아무 버튼도 찾지 못한 경우 텍스트만으로 시도
await tester.tap(find.text(buttonText));
} else {
await tester.tap(buttonFinder);
}
}
/// 스낵바 메시지 검증 헬퍼
void expectSnackBar(WidgetTester tester, String message) {
expect(
find.descendant(
of: find.byType(SnackBar),
matching: find.text(message),
),
findsOneWidget,
);
}
/// 로딩 인디케이터 검증 헬퍼
void expectLoading(WidgetTester tester, {bool isLoading = true}) {
expect(
find.byType(CircularProgressIndicator),
isLoading ? findsOneWidget : findsNothing,
);
}
/// 에러 메시지 검증 헬퍼
void expectErrorMessage(WidgetTester tester, String errorMessage) {
expect(find.text(errorMessage), findsOneWidget);
}
/// 화면 전환 대기 헬퍼
Future<void> waitForNavigation(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 300)); // 애니메이션 대기
}
/// 다이얼로그 검증 헬퍼
void expectDialog(WidgetTester tester, {String? title, String? content}) {
expect(find.byType(Dialog), findsOneWidget);
if (title != null) {
expect(
find.descendant(
of: find.byType(Dialog),
matching: find.text(title),
),
findsOneWidget,
);
}
if (content != null) {
expect(
find.descendant(
of: find.byType(Dialog),
matching: find.text(content),
),
findsOneWidget,
);
}
}
/// 다이얼로그 닫기 헬퍼
Future<void> closeDialog(WidgetTester tester) async {
// 다이얼로그 외부 탭하여 닫기
await tester.tapAt(const Offset(10, 10));
await tester.pump();
}
/// 스크롤하여 위젯 찾기 헬퍼
Future<void> scrollUntilVisible(
WidgetTester tester,
Finder finder, {
double delta = 300,
int maxScrolls = 10,
Finder? scrollable,
}) async {
final scrollableFinder = scrollable ?? find.byType(Scrollable).first;
for (int i = 0; i < maxScrolls; i++) {
if (finder.evaluate().isNotEmpty) {
return;
}
await tester.drag(scrollableFinder, Offset(0, -delta));
await tester.pump();
}
}
/// 테이블이나 리스트에서 특정 행 찾기 헬퍼
Finder findRowContaining(String text) {
return find.ancestor(
of: find.text(text),
matching: find.byType(Row),
);
}
/// 폼 필드 검증 헬퍼
void expectFormFieldError(WidgetTester tester, String fieldLabel, String errorText) {
final formField = find.ancestor(
of: find.text(fieldLabel),
matching: find.byType(TextFormField),
);
final errorFinder = find.descendant(
of: formField,
matching: find.text(errorText),
);
expect(errorFinder, findsOneWidget);
}
/// 드롭다운 선택 헬퍼
Future<void> selectDropdownItem(
WidgetTester tester,
String dropdownLabel,
String itemText,
) async {
// 드롭다운 찾기
final dropdown = find.ancestor(
of: find.text(dropdownLabel),
matching: find.byType(DropdownButtonFormField),
);
// 드롭다운 열기
await tester.tap(dropdown);
await tester.pump();
// 아이템 선택
await tester.tap(find.text(itemText).last);
await tester.pump();
}
/// 날짜 선택 헬퍼
Future<void> selectDate(
WidgetTester tester,
String dateFieldLabel,
DateTime date,
) async {
// 날짜 필드 탭
final dateField = find.ancestor(
of: find.text(dateFieldLabel),
matching: find.byType(TextFormField),
);
await tester.tap(dateField);
await tester.pump();
// 날짜 선택 (간단한 구현, 실제로는 더 복잡할 수 있음)
await tester.tap(find.text(date.day.toString()));
await tester.pump();
// 확인 버튼 탭
await tester.tap(find.text('확인'));
await tester.pump();
}