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,113 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
import 'package:superport/screens/company/controllers/company_list_controller.dart';
import 'package:superport/services/company_service.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 CompanyListController controller;
late MockMockDataService mockDataService;
late MockCompanyService mockCompanyService;
late GetIt getIt;
setUp(() {
getIt = setupTestGetIt();
mockDataService = MockMockDataService();
mockCompanyService = MockCompanyService();
// GetIt에 CompanyService 등록
getIt.registerSingleton<CompanyService>(mockCompanyService);
// Mock 설정
SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService);
SimpleMockServiceHelpers.setupCompanyServiceMock(mockCompanyService);
controller = CompanyListController(dataService: mockDataService);
});
tearDown(() {
controller.dispose();
getIt.reset();
});
group('CompanyListController 단위 테스트', () {
test('검색 키워드 업데이트 테스트', () async {
// Act
await controller.updateSearchKeyword('테스트');
// Assert
expect(controller.searchKeyword, '테스트');
});
test('회사 선택/해제 테스트', () {
// Act
controller.toggleCompanySelection(1);
expect(controller.selectedCompanyIds.contains(1), true);
controller.toggleCompanySelection(1);
expect(controller.selectedCompanyIds.contains(1), false);
});
test('전체 선택/해제 테스트', () {
// Arrange
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);
});
test('필터 적용 테스트', () {
// Arrange
controller.companies = MockDataHelpers.createMockCompanyList(count: 5);
controller.searchKeyword = '회사 1';
// Act
controller.applyFilters();
// Assert
expect(controller.filteredCompanies.length, 1);
expect(controller.filteredCompanies.first.name, '테스트 회사 1');
});
test('회사 삭제 테스트', () async {
// Arrange
controller.companies = MockDataHelpers.createMockCompanyList(count: 3);
controller.filteredCompanies = controller.companies;
// Act
final result = await controller.deleteCompany(1);
// Assert
expect(result, true);
expect(controller.companies.length, 2);
expect(controller.companies.any((c) => c.id == 1), false);
verify(mockCompanyService.deleteCompany(1)).called(1);
});
test('에러 처리 테스트', () async {
// Arrange
SimpleMockServiceHelpers.setupCompanyServiceMock(
mockCompanyService,
getCompaniesSuccess: false,
);
// Act
await controller.loadData();
// Assert
expect(controller.error, isNotNull);
expect(controller.isLoading, false);
});
});
}

View File

@@ -0,0 +1,132 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
import 'package:superport/screens/equipment/controllers/equipment_list_controller.dart';
import 'package:superport/services/equipment_service.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 EquipmentListController controller;
late MockMockDataService mockDataService;
late MockEquipmentService mockEquipmentService;
late GetIt getIt;
setUp(() {
getIt = setupTestGetIt();
mockDataService = MockMockDataService();
mockEquipmentService = MockEquipmentService();
// GetIt에 EquipmentService 등록
getIt.registerSingleton<EquipmentService>(mockEquipmentService);
// Mock 설정
when(mockDataService.getAllEquipments()).thenReturn(
MockDataHelpers.createMockUnifiedEquipmentList(count: 5)
);
// EquipmentService mock 설정
when(mockEquipmentService.getEquipments(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
status: anyNamed('status'),
companyId: anyNamed('companyId'),
warehouseLocationId: anyNamed('warehouseLocationId'),
)).thenAnswer((_) async => []);
when(mockEquipmentService.deleteEquipment(any))
.thenAnswer((_) async {});
controller = EquipmentListController(dataService: mockDataService);
});
tearDown(() {
controller.dispose();
getIt.reset();
});
group('EquipmentListController 단위 테스트', () {
test('장비 선택/해제 테스트', () {
// Arrange
final equipment = MockDataHelpers.createMockUnifiedEquipment(id: 1);
// Act
controller.selectEquipment(equipment.id, equipment.status, true);
expect(controller.selectedEquipmentIds.contains('${equipment.id}:${equipment.status}'), true);
controller.selectEquipment(equipment.id, equipment.status, false);
expect(controller.selectedEquipmentIds.contains('${equipment.id}:${equipment.status}'), false);
});
test('전체 선택 테스트', () {
// Arrange
controller.equipments = MockDataHelpers.createMockUnifiedEquipmentList(count: 3);
// 수동으로 전체 선택
for (final equipment in controller.equipments) {
controller.selectEquipment(equipment.id, equipment.status, true);
}
// Assert
expect(controller.selectedEquipmentIds.length, 3);
});
test('상태 필터 변경 테스트', () async {
// Act
await controller.changeStatusFilter('IN_STOCK');
// Assert
expect(controller.selectedStatusFilter, 'IN_STOCK');
});
test('장비 삭제 테스트', () async {
// Arrange
controller.equipments = MockDataHelpers.createMockUnifiedEquipmentList(count: 3);
final equipmentToDelete = controller.equipments.first;
// Act
final result = await controller.deleteEquipment(equipmentToDelete);
// Assert
expect(result, true);
expect(controller.equipments.length, 2);
expect(controller.equipments.any((e) => e.id == equipmentToDelete.id), false);
verify(mockEquipmentService.deleteEquipment(equipmentToDelete.equipment.id!)).called(1);
});
test('선택된 장비 수 테스트', () {
// Arrange
controller.equipments = MockDataHelpers.createMockUnifiedEquipmentList(count: 5);
// 3개만 선택
controller.selectEquipment(1, 'I', true);
controller.selectEquipment(2, 'I', true);
controller.selectEquipment(3, 'I', true);
// Assert
expect(controller.getSelectedEquipmentCount(), 3);
expect(controller.getSelectedInStockCount(), 3);
});
test('에러 처리 테스트', () async {
// Arrange
when(mockEquipmentService.getEquipments(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
status: anyNamed('status'),
companyId: anyNamed('companyId'),
warehouseLocationId: anyNamed('warehouseLocationId'),
)).thenThrow(Exception('장비 목록을 불러오는 중 오류가 발생했습니다.'));
// Act
await controller.loadData();
// Assert
expect(controller.error, isNotNull);
expect(controller.isLoading, false);
});
});
}

View File

@@ -0,0 +1,255 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
import 'package:superport/screens/user/controllers/user_list_controller.dart';
import 'package:superport/services/user_service.dart';
import 'package:superport/models/company_model.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 UserListController controller;
late MockMockDataService mockDataService;
late MockUserService mockUserService;
late GetIt getIt;
setUp(() {
getIt = setupTestGetIt();
mockDataService = MockMockDataService();
mockUserService = MockUserService();
// GetIt에 UserService 등록
getIt.registerSingleton<UserService>(mockUserService);
// Mock 설정
SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService);
SimpleMockServiceHelpers.setupUserServiceMock(mockUserService);
controller = UserListController(dataService: mockDataService);
});
tearDown(() {
controller.dispose();
getIt.reset();
});
group('UserListController 단위 테스트', () {
test('초기 상태 확인', () {
expect(controller.users, isEmpty);
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.hasMoreData, true);
expect(controller.searchQuery, '');
expect(controller.filterCompanyId, isNull);
expect(controller.filterRole, isNull);
expect(controller.filterIsActive, isNull);
});
test('사용자 목록 로드 테스트', () async {
// Act
await controller.loadUsers();
// Assert
expect(controller.users, isNotEmpty);
expect(controller.users.length, 10);
expect(controller.isLoading, false);
expect(controller.error, isNull);
verify(mockUserService.getUsers(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
role: null,
)).called(1);
});
test('검색 쿼리 설정 및 검색 테스트', () async {
// Act
controller.setSearchQuery('사용자 1');
// Assert
expect(controller.searchQuery, '사용자 1');
await Future.delayed(Duration(milliseconds: 100)); // loadUsers 완료 대기
verify(mockUserService.getUsers(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
role: null,
)).called(1);
});
test('필터 설정 테스트', () async {
// Act
controller.setFilters(
companyId: 1,
role: 'S',
isActive: true,
);
// Assert
expect(controller.filterCompanyId, 1);
expect(controller.filterRole, 'S');
expect(controller.filterIsActive, true);
await Future.delayed(Duration(milliseconds: 100)); // loadUsers 완료 대기
verify(mockUserService.getUsers(
page: 1,
perPage: 20,
isActive: true,
companyId: 1,
role: 'S',
)).called(1);
});
test('필터 초기화 테스트', () async {
// Arrange
controller.setFilters(
companyId: 1,
role: 'S',
isActive: true,
);
await Future.delayed(Duration(milliseconds: 100));
// Act
controller.clearFilters();
// Assert
expect(controller.filterCompanyId, isNull);
expect(controller.filterRole, isNull);
expect(controller.filterIsActive, isNull);
expect(controller.searchQuery, '');
});
test('사용자 삭제 테스트', () async {
// Arrange
await controller.loadUsers();
final initialUserCount = controller.users.length;
bool deletedCallbackCalled = false;
// Act
await controller.deleteUser(
1,
() => deletedCallbackCalled = true,
(error) => fail('삭제 실패: $error'),
);
// Assert
expect(deletedCallbackCalled, true);
expect(controller.users.length, initialUserCount - 1);
expect(controller.users.any((u) => u.id == 1), false);
verify(mockUserService.deleteUser(1)).called(1);
});
test('사용자 상태 변경 테스트', () async {
// Arrange
await controller.loadUsers();
// Act
await controller.changeUserStatus(
1,
false,
(error) => fail('상태 변경 실패: $error'),
);
// Assert
final updatedUser = controller.users.firstWhere((u) => u.id == 1);
expect(updatedUser.isActive, false);
verify(mockUserService.changeUserStatus(1, false)).called(1);
});
test('에러 처리 테스트', () async {
// Arrange
SimpleMockServiceHelpers.setupUserServiceMock(
mockUserService,
getUsersSuccess: false,
);
// Act
await controller.loadUsers();
// Assert
expect(controller.error, isNotNull);
expect(controller.isLoading, false);
expect(controller.users, isEmpty);
});
test('페이지네이션 - 더 불러오기 테스트', () async {
// Arrange
// 첫 번째 페이지와 두 번째 페이지에 대해 다른 응답 설정
when(mockUserService.getUsers(
page: 1,
perPage: 20,
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
role: anyNamed('role'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockUserModelList(count: 20),
);
when(mockUserService.getUsers(
page: 2,
perPage: 20,
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
role: anyNamed('role'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockUserModelList(count: 10),
);
await controller.loadUsers();
final initialCount = controller.users.length;
// Act
await controller.loadMore();
// Assert
expect(controller.users.length, greaterThan(initialCount));
verify(mockUserService.getUsers(
page: 2,
perPage: 20,
isActive: null,
companyId: null,
role: null,
)).called(1);
});
test('Mock 모드에서 필터링 테스트', () async {
// Arrange
controller.toggleApiMode(); // Mock 모드로 전환
// Act
controller.setFilters(companyId: 1, role: 'S');
await Future.delayed(Duration(milliseconds: 100));
// Assert
// Mock 모드에서는 getAllUsers를 통해 전체 데이터를 가져온 후 필터링
verify(mockDataService.getAllUsers()).called(greaterThanOrEqualTo(1));
});
test('지점명 조회 테스트', () {
// Arrange
final mockCompany = Company(
id: 1,
name: '테스트 회사',
branches: [
Branch(
id: 1,
companyId: 1,
name: '본사',
),
],
);
when(mockDataService.getCompanyById(1)).thenReturn(mockCompany);
// Act
final branchName = controller.getBranchName(1, 1);
// Assert
expect(branchName, '본사');
verify(mockDataService.getCompanyById(1)).called(1);
});
});
}