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>
This commit is contained in:
JiWoong Sul
2025-07-31 20:37:26 +09:00
parent f08b7fec79
commit d6f34c0a52
14 changed files with 6469 additions and 2 deletions

View File

@@ -0,0 +1,413 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:superport/screens/company/company_list_redesign.dart';
import 'package:superport/screens/company/controllers/company_list_controller.dart';
import 'package:superport/services/company_service.dart';
import 'package:superport/services/auth_service.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/models/company_model.dart';
import 'package:superport/models/address_model.dart';
import 'package:superport/core/errors/failures.dart';
import '../../helpers/test_helpers.dart';
import '../../helpers/simple_mock_services.dart';
import '../../helpers/simple_mock_services.mocks.dart';
import '../../helpers/mock_data_helpers.dart';
void main() {
late MockCompanyService mockCompanyService;
late MockAuthService mockAuthService;
late MockMockDataService mockDataService;
late GetIt getIt;
setUp(() {
// GetIt 초기화
getIt = setupTestGetIt();
// Mock 서비스 생성
mockCompanyService = MockCompanyService();
mockAuthService = MockAuthService();
mockDataService = MockMockDataService();
// Mock 서비스 등록
getIt.registerSingleton<CompanyService>(mockCompanyService);
getIt.registerSingleton<AuthService>(mockAuthService);
getIt.registerSingleton<MockDataService>(mockDataService);
// 기본 Mock 설정
SimpleMockServiceHelpers.setupAuthServiceMock(mockAuthService, isLoggedIn: true);
SimpleMockServiceHelpers.setupCompanyServiceMock(mockCompanyService);
SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService);
});
tearDown(() {
getIt.reset();
});
group('회사 목록 화면 Widget 테스트', () {
testWidgets('초기 화면 렌더링 테스트', (WidgetTester tester) async {
// Arrange & Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// Assert
expect(find.text('회사 관리'), findsOneWidget); // 앱바 타이틀
expect(find.byType(TextField), findsOneWidget); // 검색 필드
expect(find.byIcon(Icons.add), findsOneWidget); // 추가 버튼
expect(find.byType(DataTable), findsOneWidget); // 데이터 테이블
});
testWidgets('회사 목록 로딩 및 표시 테스트', (WidgetTester tester) async {
// Arrange
final mockCompanies = MockDataHelpers.createMockCompanyList(count: 5);
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => mockCompanies);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// Assert
// 각 회사가 테이블에 표시되는지 확인
for (int i = 0; i < 5; i++) {
expect(find.text('테스트 회사 ${i + 1}'), findsOneWidget);
expect(find.text('담당자 ${i + 1}'), findsOneWidget);
expect(find.text('02-${1000 + i}-${5678 + i}'), findsOneWidget);
}
});
testWidgets('회사 검색 기능 테스트', (WidgetTester tester) async {
// Arrange
final allCompanies = MockDataHelpers.createMockCompanyList(count: 10);
final searchedCompanies = [allCompanies[0]]; // 검색 결과로 첫 번째 회사만
// 초기 로드
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => allCompanies);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// 검색어 입력
final searchField = find.byType(TextField);
await tester.enterText(searchField, '테스트 회사 1');
// 검색 결과 설정
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: '테스트 회사 1',
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => searchedCompanies);
// 디바운스 대기
await tester.pump(const Duration(milliseconds: 600));
await pumpAndSettleWithTimeout(tester);
// Assert
expect(find.text('테스트 회사 1'), findsOneWidget);
expect(find.text('테스트 회사 2'), findsNothing);
});
testWidgets('회사 추가 버튼 클릭 테스트', (WidgetTester tester) async {
// Arrange
bool navigated = false;
await pumpTestWidget(
tester,
const CompanyListRedesign(),
routes: {
'/company/add': (context) {
navigated = true;
return const Scaffold(body: Text('회사 추가 화면'));
},
},
);
await pumpAndSettleWithTimeout(tester);
// Act
final addButton = find.byIcon(Icons.add);
await tester.tap(addButton);
await pumpAndSettleWithTimeout(tester);
// Assert
expect(navigated, true);
expect(find.text('회사 추가 화면'), findsOneWidget);
});
testWidgets('회사 삭제 다이얼로그 테스트', (WidgetTester tester) async {
// Arrange
final companies = MockDataHelpers.createMockCompanyList(count: 1);
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => companies);
when(mockCompanyService.deleteCompany(any))
.thenAnswer((_) async => null);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// 삭제 버튼 찾기 및 클릭
final deleteButton = find.byIcon(Icons.delete).first;
await tester.tap(deleteButton);
await pumpAndSettleWithTimeout(tester);
// Assert - 삭제 확인 다이얼로그
expectDialog(tester, title: '삭제 확인', content: '이 회사 정보를 삭제하시겠습니까?');
// 삭제 확인
await tapButtonByText(tester, '삭제');
await pumpAndSettleWithTimeout(tester);
// 삭제 메서드 호출 확인
verify(mockCompanyService.deleteCompany(1)).called(1);
});
testWidgets('회사 정보 수정 화면 이동 테스트', (WidgetTester tester) async {
// Arrange
final companies = MockDataHelpers.createMockCompanyList(count: 1);
bool navigated = false;
int? companyId;
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => companies);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
routes: {
'/company/edit': (context) {
navigated = true;
companyId = ModalRoute.of(context)!.settings.arguments as int?;
return const Scaffold(body: Text('회사 수정 화면'));
},
},
);
await pumpAndSettleWithTimeout(tester);
// 수정 버튼 찾기 및 클릭
final editButton = find.byIcon(Icons.edit).first;
await tester.tap(editButton);
await pumpAndSettleWithTimeout(tester);
// Assert
expect(navigated, true);
expect(companyId, 1);
expect(find.text('회사 수정 화면'), findsOneWidget);
});
testWidgets('회사 목록 페이지네이션 테스트', (WidgetTester tester) async {
// Arrange
final firstPageCompanies = MockDataHelpers.createMockCompanyList(count: 20);
final secondPageCompanies = MockDataHelpers.createMockCompanyList(count: 5)
.map((c) => MockDataHelpers.createMockCompany(
id: c.id! + 20,
name: '추가 회사 ${c.id}',
))
.toList();
// 첫 페이지
when(mockCompanyService.getCompanies(
page: 1,
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => firstPageCompanies);
// 두 번째 페이지
when(mockCompanyService.getCompanies(
page: 2,
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => secondPageCompanies);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// 스크롤하여 더 많은 데이터 로드
final scrollable = find.byType(SingleChildScrollView).first;
await tester.drag(scrollable, const Offset(0, -500));
await pumpAndSettleWithTimeout(tester);
// Assert
verify(mockCompanyService.getCompanies(
page: 1,
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).called(greaterThanOrEqualTo(1));
});
testWidgets('에러 처리 테스트', (WidgetTester tester) async {
// Arrange
SimpleMockServiceHelpers.setupCompanyServiceMock(
mockCompanyService,
getCompaniesSuccess: false,
);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// Assert
expect(find.text('회사 목록을 불러오는 중 오류가 발생했습니다.'), findsOneWidget);
});
testWidgets('로딩 상태 표시 테스트', (WidgetTester tester) async {
// Arrange
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async {
await Future.delayed(const Duration(seconds: 2));
return MockDataHelpers.createMockCompanyList(count: 5);
});
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await tester.pump(); // 로딩 시작
// Assert - 로딩 인디케이터 표시
expectLoading(tester, isLoading: true);
// 로딩 완료 대기
await pumpAndSettleWithTimeout(tester);
// Assert - 로딩 인디케이터 사라짐
expectLoading(tester, isLoading: false);
});
testWidgets('회사 선택 체크박스 테스트', (WidgetTester tester) async {
// Arrange
final companies = MockDataHelpers.createMockCompanyList(count: 3);
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async => companies);
// Act
await pumpTestWidget(
tester,
const CompanyListRedesign(),
);
await pumpAndSettleWithTimeout(tester);
// 첫 번째 체크박스 선택
final firstCheckbox = find.byType(Checkbox).at(1); // 헤더 체크박스 제외
await tester.tap(firstCheckbox);
await pumpAndSettleWithTimeout(tester);
// 전체 선택 체크박스 클릭
final selectAllCheckbox = find.byType(Checkbox).first;
await tester.tap(selectAllCheckbox);
await pumpAndSettleWithTimeout(tester);
// Assert
// 모든 체크박스가 선택되었는지 확인하는 로직 추가
final checkboxes = find.byType(Checkbox);
expect(checkboxes, findsNWidgets(4)); // 헤더 + 3개 회사
});
});
group('회사 컨트롤러 단위 테스트', () {
test('검색 키워드 업데이트 테스트', () async {
// Arrange
final controller = CompanyListController(dataService: MockMockDataService());
// Act
controller.updateSearchKeyword('테스트');
// Assert
expect(controller.searchKeyword, '테스트');
});
test('회사 선택/해제 테스트', () {
// Arrange
final controller = CompanyListController(dataService: MockMockDataService());
// Act
controller.toggleCompanySelection(1);
expect(controller.selectedCompanyIds.contains(1), true);
controller.toggleCompanySelection(1);
expect(controller.selectedCompanyIds.contains(1), false);
});
test('전체 선택/해제 테스트', () {
// Arrange
final controller = CompanyListController(dataService: MockMockDataService());
controller.companies = MockDataHelpers.createMockCompanyList(count: 3);
controller.filteredCompanies = controller.companies;
// Act - 전체 선택
controller.toggleSelectAll();
expect(controller.selectedCompanyIds.length, 3);
// Act - 전체 해제
controller.toggleSelectAll();
expect(controller.selectedCompanyIds.isEmpty, true);
});
});
}