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,614 @@
import 'package:superport/data/models/auth/auth_user.dart';
import 'package:superport/data/models/auth/login_response.dart';
import 'package:superport/data/models/company/company_dto.dart' as company_dto;
// import 'package:superport/data/models/company/branch_dto.dart';
import 'package:superport/data/models/equipment/equipment_response.dart';
import 'package:superport/data/models/equipment/equipment_io_response.dart';
import 'package:superport/data/models/user/user_dto.dart';
import 'package:superport/data/models/license/license_dto.dart';
import 'package:superport/data/models/warehouse/warehouse_dto.dart';
import 'package:superport/data/models/common/paginated_response.dart';
import 'package:superport/models/company_model.dart';
import 'package:superport/models/address_model.dart';
import 'package:superport/models/equipment_unified_model.dart';
import 'package:superport/models/user_model.dart';
import 'package:superport/models/warehouse_location_model.dart';
import 'package:superport/models/license_model.dart';
/// 테스트용 Mock 데이터 생성 헬퍼
class MockDataHelpers {
/// Mock 사용자 생성
static AuthUser createMockUser({
int id = 1,
String username = 'testuser',
String email = 'test@example.com',
String name = '테스트 사용자',
String role = 'USER',
}) {
return AuthUser(
id: id,
username: username,
email: email,
name: name,
role: role,
);
}
/// Mock 로그인 응답 생성
static LoginResponse createMockLoginResponse({
String accessToken = 'test_access_token',
String refreshToken = 'test_refresh_token',
String tokenType = 'Bearer',
int expiresIn = 3600,
AuthUser? user,
}) {
return LoginResponse(
accessToken: accessToken,
refreshToken: refreshToken,
tokenType: tokenType,
expiresIn: expiresIn,
user: user ?? createMockUser(),
);
}
/// Mock 회사 생성
static Company createMockCompany({
int id = 1,
String name = '테스트 회사',
String? contactName,
String? contactPosition,
String? contactPhone,
String? contactEmail,
String? addressStr,
String? remark,
List<CompanyType>? companyTypes,
}) {
return Company(
id: id,
name: name,
address: Address(
zipCode: '06234',
region: addressStr ?? '서울특별시 강남구 테헤란로 123',
detailAddress: '테스트빌딩 5층',
),
contactName: contactName ?? '홍길동',
contactPosition: contactPosition ?? '대표이사',
contactPhone: contactPhone ?? '02-1234-5678',
contactEmail: contactEmail ?? 'company@test.com',
companyTypes: companyTypes ?? [CompanyType.customer],
remark: remark,
);
}
/// Mock 지점 생성
static Branch createMockBranch({
int id = 1,
int companyId = 1,
String name = '본사',
String? addressStr,
String? contactName,
String? contactPosition,
String? contactPhone,
String? contactEmail,
}) {
return Branch(
id: id,
companyId: companyId,
name: name,
address: Address(
zipCode: '06234',
region: addressStr ?? '서울특별시 강남구 테헤란로 123',
detailAddress: '테스트빌딩 5층',
),
contactName: contactName ?? '김지점',
contactPosition: contactPosition ?? '지점장',
contactPhone: contactPhone ?? '02-1234-5678',
contactEmail: contactEmail ?? 'branch@test.com',
);
}
/// Mock 장비 생성
static EquipmentResponse createMockEquipment({
int id = 1,
String equipmentNumber = 'EQ001',
String? category1,
String? category2,
String? category3,
String manufacturer = '삼성전자',
String? modelName,
String? serialNumber,
String? barcode,
DateTime? purchaseDate,
double purchasePrice = 1000000,
String status = 'AVAILABLE',
int? currentCompanyId,
int? currentBranchId,
int? warehouseLocationId,
String? remark,
String? companyName,
String? branchName,
String? warehouseName,
}) {
return EquipmentResponse(
id: id,
equipmentNumber: equipmentNumber,
category1: category1 ?? '전자기기',
category2: category2,
category3: category3,
manufacturer: manufacturer,
modelName: modelName ?? 'TEST-MODEL-001',
serialNumber: serialNumber ?? 'SN123456789',
barcode: barcode,
purchaseDate: purchaseDate ?? DateTime.now().subtract(const Duration(days: 30)),
purchasePrice: purchasePrice,
status: status,
currentCompanyId: currentCompanyId,
currentBranchId: currentBranchId,
warehouseLocationId: warehouseLocationId,
remark: remark,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
companyName: companyName,
branchName: branchName,
warehouseName: warehouseName,
);
}
/// Mock 사용자 DTO 생성
static UserDto createMockUserDto({
int id = 1,
String username = 'testuser',
String email = 'test@example.com',
String name = '테스트 사용자',
String? phone,
String role = 'staff',
bool isActive = true,
int? companyId,
int? branchId,
}) {
return UserDto(
id: id,
username: username,
email: email,
name: name,
phone: phone ?? '010-1234-5678',
role: role,
isActive: isActive,
companyId: companyId ?? 1,
branchId: branchId ?? 1,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
}
/// Mock 라이선스 생성
static LicenseDto createMockLicense({
int id = 1,
String licenseKey = 'TEST-LICENSE-KEY-12345',
String? productName,
String? vendor,
String? licenseType,
int? userCount,
DateTime? purchaseDate,
DateTime? expiryDate,
double? purchasePrice,
int? companyId,
int? branchId,
int? assignedUserId,
String? remark,
bool isActive = true,
}) {
return LicenseDto(
id: id,
licenseKey: licenseKey,
productName: productName ?? '테스트 라이선스',
vendor: vendor ?? '테스트 벤더',
licenseType: licenseType ?? 'SOFTWARE',
userCount: userCount ?? 10,
purchaseDate: purchaseDate ?? DateTime.now().subtract(const Duration(days: 365)),
expiryDate: expiryDate ?? DateTime.now().add(const Duration(days: 365)),
purchasePrice: purchasePrice ?? 500000,
companyId: companyId,
branchId: branchId,
assignedUserId: assignedUserId,
remark: remark,
isActive: isActive,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
}
/// Mock 창고 위치 생성 (WarehouseDto가 없으므로 Map 반환)
static Map<String, dynamic> createMockWarehouse({
int id = 1,
String code = 'WH001',
String name = '메인 창고',
String type = 'WAREHOUSE',
String? location,
int? capacity,
int? currentOccupancy,
String? manager,
String? contactNumber,
bool isActive = true,
String? notes,
}) {
return {
'id': id,
'code': code,
'name': name,
'type': type,
'location': location ?? '서울시 강서구 물류단지',
'capacity': capacity ?? 1000,
'currentOccupancy': currentOccupancy ?? 250,
'manager': manager ?? '김창고',
'contactNumber': contactNumber ?? '02-9876-5432',
'isActive': isActive,
'notes': notes,
'createdAt': DateTime.now().toIso8601String(),
'updatedAt': DateTime.now().toIso8601String(),
};
}
/// Mock 페이지네이션 응답 생성
static PaginatedResponse<T> createMockPaginatedResponse<T>({
required List<T> data,
int page = 1,
int size = 20,
int? totalElements,
int? totalPages,
}) {
final total = totalElements ?? data.length;
final pages = totalPages ?? ((total + size - 1) ~/ size);
return PaginatedResponse<T>(
items: data,
page: page,
size: size,
totalElements: total,
totalPages: pages,
first: page == 1,
last: page >= pages,
);
}
/// Mock 장비 목록 생성
static List<EquipmentResponse> createMockEquipmentList({int count = 10}) {
return List.generate(
count,
(index) => createMockEquipment(
id: index + 1,
equipmentNumber: 'EQ${(index + 1).toString().padLeft(3, '0')}',
category1: '전자기기',
modelName: '테스트 장비 ${index + 1}',
serialNumber: 'SN${DateTime.now().millisecondsSinceEpoch}${index}',
status: index % 3 == 0 ? 'IN_USE' : 'AVAILABLE',
),
);
}
/// Mock UnifiedEquipment 생성
static UnifiedEquipment createMockUnifiedEquipment({
int id = 1,
int equipmentId = 1,
String manufacturer = '삼성전자',
String name = '노트북',
String category = 'IT장비',
String subCategory = '컴퓨터',
String subSubCategory = '노트북',
String? serialNumber,
int quantity = 1,
String status = 'I', // I: 입고, O: 출고, R: 대여
DateTime? date,
String? notes,
}) {
final equipment = Equipment(
id: equipmentId,
manufacturer: manufacturer,
name: name,
category: category,
subCategory: subCategory,
subSubCategory: subSubCategory,
serialNumber: serialNumber ?? 'SN${DateTime.now().millisecondsSinceEpoch}',
quantity: quantity,
inDate: date ?? DateTime.now(),
);
return UnifiedEquipment(
id: id,
equipment: equipment,
date: date ?? DateTime.now(),
status: status,
notes: notes,
);
}
/// Mock UnifiedEquipment 목록 생성
static List<UnifiedEquipment> createMockUnifiedEquipmentList({int count = 10}) {
return List.generate(
count,
(index) => createMockUnifiedEquipment(
id: index + 1,
equipmentId: index + 1,
name: '테스트 장비 ${index + 1}',
status: index % 3 == 0 ? 'O' : 'I', // 일부는 출고, 대부분은 입고 상태
serialNumber: 'SN${DateTime.now().millisecondsSinceEpoch}${index}',
),
);
}
/// Mock 회사 목록 생성
static List<Company> createMockCompanyList({int count = 10}) {
return List.generate(
count,
(index) => createMockCompany(
id: index + 1,
name: '테스트 회사 ${index + 1}',
contactName: '담당자 ${index + 1}',
contactPosition: '대리',
contactPhone: '02-${1000 + index}-${5678 + index}',
),
);
}
/// Mock 사용자 목록 생성
static List<UserDto> createMockUserList({int count = 10}) {
return List.generate(
count,
(index) => createMockUserDto(
id: index + 1,
username: 'user${index + 1}',
email: 'user${index + 1}@test.com',
name: '사용자 ${index + 1}',
phone: '010-${1000 + index}-${1000 + index}',
),
);
}
/// Mock 라이선스 목록 생성
static List<LicenseDto> createMockLicenseList({int count = 10}) {
return List.generate(
count,
(index) => createMockLicense(
id: index + 1,
productName: '라이선스 ${index + 1}',
licenseKey: 'KEY-${DateTime.now().millisecondsSinceEpoch}-${index}',
licenseType: index % 2 == 0 ? 'SOFTWARE' : 'HARDWARE',
isActive: index % 4 != 0,
),
);
}
/// Mock 창고 목록 생성
static List<dynamic> createMockWarehouseList({int count = 5}) {
return List.generate(
count,
(index) => createMockWarehouse(
id: index + 1,
code: 'WH${(index + 1).toString().padLeft(3, '0')}',
name: '창고 ${index + 1}',
type: index % 2 == 0 ? 'WAREHOUSE' : 'STORAGE',
currentOccupancy: (index + 1) * 50,
),
);
}
/// Mock Equipment 모델 생성
static Equipment createMockEquipmentModel({
int? id,
String manufacturer = '삼성전자',
String name = '노트북',
String category = 'IT장비',
String subCategory = '컴퓨터',
String subSubCategory = '노트북',
String? serialNumber,
String? barcode,
int quantity = 1,
DateTime? inDate,
String? remark,
String? warrantyLicense,
DateTime? warrantyStartDate,
DateTime? warrantyEndDate,
}) {
return Equipment(
id: id ?? 1,
manufacturer: manufacturer,
name: name,
category: category,
subCategory: subCategory,
subSubCategory: subSubCategory,
serialNumber: serialNumber ?? 'SN${DateTime.now().millisecondsSinceEpoch}',
barcode: barcode,
quantity: quantity,
inDate: inDate ?? DateTime.now(),
remark: remark,
warrantyLicense: warrantyLicense,
warrantyStartDate: warrantyStartDate,
warrantyEndDate: warrantyEndDate,
);
}
/// Mock Equipment 모델 목록 생성
static List<Equipment> createMockEquipmentModelList({int count = 10}) {
return List.generate(
count,
(index) => createMockEquipmentModel(
id: index + 1,
name: '테스트 장비 ${index + 1}',
category: index % 2 == 0 ? 'IT장비' : '사무용품',
serialNumber: 'SN${DateTime.now().millisecondsSinceEpoch}${index}',
),
);
}
/// Mock User 모델 생성 (UserDto가 아닌 User 모델)
static User createMockUserModel({
int? id,
int companyId = 1,
int? branchId,
String name = '테스트 사용자',
String role = 'M',
String? position,
String? email,
List<Map<String, String>>? phoneNumbers,
String? username,
bool isActive = true,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return User(
id: id ?? 1,
companyId: companyId,
branchId: branchId ?? 1,
name: name,
role: role,
position: position ?? '대리',
email: email ?? 'user@test.com',
phoneNumbers: phoneNumbers ?? [{'type': '기본', 'number': '010-1234-5678'}],
username: username ?? 'testuser',
isActive: isActive,
createdAt: createdAt ?? DateTime.now(),
updatedAt: updatedAt ?? DateTime.now(),
);
}
/// Mock User 모델 목록 생성
static List<User> createMockUserModelList({int count = 10}) {
return List.generate(
count,
(index) => createMockUserModel(
id: index + 1,
name: '사용자 ${index + 1}',
username: 'user${index + 1}',
email: 'user${index + 1}@test.com',
role: index % 3 == 0 ? 'S' : 'M',
isActive: index % 5 != 0,
phoneNumbers: [{'type': '기본', 'number': '010-${1000 + index}-${1000 + index}'}],
),
);
}
/// Mock 장비 입출고 응답 생성
static EquipmentIoResponse createMockEquipmentIoResponse({
bool success = true,
String? message,
int transactionId = 1,
int equipmentId = 1,
int quantity = 1,
String transactionType = 'IN',
DateTime? transactionDate,
}) {
return EquipmentIoResponse(
success: success,
message: message ?? (success ? '장비 처리가 완료되었습니다.' : '장비 처리에 실패했습니다.'),
transactionId: transactionId,
equipmentId: equipmentId,
quantity: quantity,
transactionType: transactionType,
transactionDate: transactionDate ?? DateTime.now(),
);
}
/// Mock 창고 용량 정보 생성
static WarehouseCapacityInfo createMockWarehouseCapacityInfo({
int warehouseId = 1,
int totalCapacity = 1000,
int usedCapacity = 250,
int? availableCapacity,
double? usagePercentage,
int equipmentCount = 50,
}) {
final available = availableCapacity ?? (totalCapacity - usedCapacity);
final percentage = usagePercentage ?? (usedCapacity / totalCapacity * 100);
return WarehouseCapacityInfo(
warehouseId: warehouseId,
totalCapacity: totalCapacity,
usedCapacity: usedCapacity,
availableCapacity: available,
usagePercentage: percentage,
equipmentCount: equipmentCount,
);
}
/// Mock WarehouseLocation 생성
static WarehouseLocation createMockWarehouseLocation({
int id = 1,
String name = '메인 창고',
String? addressStr,
String? remark,
}) {
return WarehouseLocation(
id: id,
name: name,
address: Address(
zipCode: '12345',
region: addressStr ?? '서울시 강서구 물류단지',
detailAddress: '창고동 A동',
),
remark: remark,
);
}
/// Mock License 모델 생성
static License createMockLicenseModel({
int? id,
String licenseKey = 'TEST-LICENSE-KEY',
String productName = '테스트 라이선스',
String? vendor,
String? licenseType,
int? userCount,
DateTime? purchaseDate,
DateTime? expiryDate,
double? purchasePrice,
int? companyId,
int? branchId,
int? assignedUserId,
String? remark,
bool isActive = true,
}) {
return License(
id: id ?? 1,
licenseKey: licenseKey,
productName: productName,
vendor: vendor ?? '테스트 벤더',
licenseType: licenseType ?? 'SOFTWARE',
userCount: userCount ?? 10,
purchaseDate: purchaseDate ?? DateTime.now().subtract(const Duration(days: 365)),
expiryDate: expiryDate ?? DateTime.now().add(const Duration(days: 365)),
purchasePrice: purchasePrice ?? 500000,
companyId: companyId ?? 1,
branchId: branchId,
assignedUserId: assignedUserId,
remark: remark,
isActive: isActive,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
}
/// Mock License 모델 목록 생성
static List<License> createMockLicenseModelList({int count = 10}) {
return List.generate(
count,
(index) => createMockLicenseModel(
id: index + 1,
productName: '라이선스 ${index + 1}',
licenseKey: 'KEY-${DateTime.now().millisecondsSinceEpoch}-${index}',
licenseType: index % 2 == 0 ? 'SOFTWARE' : 'HARDWARE',
isActive: index % 4 != 0,
),
);
}
/// Mock WarehouseLocation 목록 생성
static List<WarehouseLocation> createMockWarehouseLocationList({int count = 5}) {
return List.generate(
count,
(index) => createMockWarehouseLocation(
id: index + 1,
name: '창고 ${index + 1}',
addressStr: '서울시 ${index % 3 == 0 ? "강서구" : index % 3 == 1 ? "강남구" : "송파구"} 물류단지 ${index + 1}',
),
);
}
}

View File

@@ -0,0 +1,525 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:dartz/dartz.dart';
import 'package:superport/services/auth_service.dart';
import 'package:superport/services/company_service.dart';
import 'package:superport/services/equipment_service.dart';
import 'package:superport/services/user_service.dart';
import 'package:superport/services/license_service.dart';
import 'package:superport/services/warehouse_service.dart';
import 'package:superport/services/dashboard_service.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/core/errors/failures.dart';
import 'mock_data_helpers.dart';
import 'mock_services.mocks.dart';
// Mockito 어노테이션으로 Mock 클래스 생성
@GenerateMocks([
AuthService,
CompanyService,
EquipmentService,
UserService,
LicenseService,
WarehouseService,
DashboardService,
MockDataService,
])
void main() {}
/// Mock 서비스 설정 헬퍼
class MockServiceHelpers {
/// AuthService Mock 설정
static void setupAuthServiceMock(
MockAuthService mockAuthService, {
bool isLoggedIn = false,
bool loginSuccess = true,
bool logoutSuccess = true,
}) {
// isLoggedIn
when(mockAuthService.isLoggedIn())
.thenAnswer((_) async => isLoggedIn);
// login
if (loginSuccess) {
when(mockAuthService.login(any))
.thenAnswer((_) async => Right(MockDataHelpers.createMockLoginResponse()));
} else {
when(mockAuthService.login(any))
.thenAnswer((_) async => Left(AuthenticationFailure(
message: '이메일 또는 비밀번호가 올바르지 않습니다.',
)));
}
// logout
if (logoutSuccess) {
when(mockAuthService.logout())
.thenAnswer((_) async => const Right(null));
} else {
when(mockAuthService.logout())
.thenAnswer((_) async => Left(ServerFailure(
message: '로그아웃 중 오류가 발생했습니다.',
)));
}
// getCurrentUser
when(mockAuthService.getCurrentUser())
.thenAnswer((_) async => isLoggedIn ? MockDataHelpers.createMockUser() : null);
// getAccessToken
when(mockAuthService.getAccessToken())
.thenAnswer((_) async => isLoggedIn ? 'test_access_token' : null);
// authStateChanges
when(mockAuthService.authStateChanges)
.thenAnswer((_) => Stream.value(isLoggedIn));
}
/// CompanyService Mock 설정
static void setupCompanyServiceMock(
MockCompanyService mockCompanyService, {
bool getCompaniesSuccess = true,
bool createCompanySuccess = true,
bool updateCompanySuccess = true,
bool deleteCompanySuccess = true,
int companyCount = 10,
}) {
// getCompanies
if (getCompaniesSuccess) {
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockCompanyList(count: companyCount),
);
} else {
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenThrow(
ServerFailure(message: '회사 목록을 불러오는 중 오류가 발생했습니다.'),
);
}
// createCompany
if (createCompanySuccess) {
when(mockCompanyService.createCompany(any))
.thenAnswer((_) async => MockDataHelpers.createMockCompany());
} else {
when(mockCompanyService.createCompany(any))
.thenThrow(ServerFailure(
message: '회사 등록 중 오류가 발생했습니다.',
));
}
// updateCompany
if (updateCompanySuccess) {
when(mockCompanyService.updateCompany(any, any))
.thenAnswer((_) async => MockDataHelpers.createMockCompany());
} else {
when(mockCompanyService.updateCompany(any, any))
.thenThrow(ServerFailure(
message: '회사 정보 수정 중 오류가 발생했습니다.',
));
}
// deleteCompany
if (deleteCompanySuccess) {
when(mockCompanyService.deleteCompany(any))
.thenAnswer((_) async {});
} else {
when(mockCompanyService.deleteCompany(any))
.thenThrow(ServerFailure(
message: '회사 삭제 중 오류가 발생했습니다.',
));
}
// getCompanyDetail
when(mockCompanyService.getCompanyDetail(any))
.thenAnswer((_) async => MockDataHelpers.createMockCompany());
// checkDuplicateCompany
when(mockCompanyService.checkDuplicateCompany(any))
.thenAnswer((_) async => false);
// getCompanyNames
when(mockCompanyService.getCompanyNames())
.thenAnswer((_) async => [{'id': 1, 'name': '테스트 회사 1'}, {'id': 2, 'name': '테스트 회사 2'}, {'id': 3, 'name': '테스트 회사 3'}]);
}
/// EquipmentService Mock 설정
static void setupEquipmentServiceMock(
MockEquipmentService mockEquipmentService, {
bool getEquipmentsSuccess = true,
bool createEquipmentSuccess = true,
bool equipmentInSuccess = true,
bool equipmentOutSuccess = true,
int equipmentCount = 10,
}) {
// getEquipments
if (getEquipmentsSuccess) {
when(mockEquipmentService.getEquipments(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
status: anyNamed('status'),
companyId: anyNamed('companyId'),
warehouseLocationId: anyNamed('warehouseLocationId'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockEquipmentModelList(count: equipmentCount),
);
} else {
when(mockEquipmentService.getEquipments(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
status: anyNamed('status'),
companyId: anyNamed('companyId'),
warehouseLocationId: anyNamed('warehouseLocationId'),
)).thenThrow(ServerFailure(
message: '장비 목록을 불러오는 중 오류가 발생했습니다.',
));
}
// createEquipment
if (createEquipmentSuccess) {
when(mockEquipmentService.createEquipment(any))
.thenAnswer((_) async => MockDataHelpers.createMockEquipmentModel());
} else {
when(mockEquipmentService.createEquipment(any))
.thenThrow(ServerFailure(
message: '장비 등록 중 오류가 발생했습니다.',
));
}
// equipmentIn
if (equipmentInSuccess) {
when(mockEquipmentService.equipmentIn(
equipmentId: anyNamed('equipmentId'),
quantity: anyNamed('quantity'),
warehouseLocationId: anyNamed('warehouseLocationId'),
notes: anyNamed('notes'),
)).thenAnswer((_) async => MockDataHelpers.createMockEquipmentIoResponse());
} else {
when(mockEquipmentService.equipmentIn(
equipmentId: anyNamed('equipmentId'),
quantity: anyNamed('quantity'),
warehouseLocationId: anyNamed('warehouseLocationId'),
notes: anyNamed('notes'),
)).thenThrow(ServerFailure(
message: '장비 입고 처리 중 오류가 발생했습니다.',
));
}
// equipmentOut
if (equipmentOutSuccess) {
when(mockEquipmentService.equipmentOut(
equipmentId: anyNamed('equipmentId'),
quantity: anyNamed('quantity'),
companyId: anyNamed('companyId'),
branchId: anyNamed('branchId'),
notes: anyNamed('notes'),
)).thenAnswer((_) async => MockDataHelpers.createMockEquipmentIoResponse());
} else {
when(mockEquipmentService.equipmentOut(
equipmentId: anyNamed('equipmentId'),
quantity: anyNamed('quantity'),
companyId: anyNamed('companyId'),
branchId: anyNamed('branchId'),
notes: anyNamed('notes'),
)).thenThrow(ServerFailure(
message: '장비 출고 처리 중 오류가 발생했습니다.',
));
}
// getEquipmentDetail
when(mockEquipmentService.getEquipmentDetail(any))
.thenAnswer((_) async => MockDataHelpers.createMockEquipmentModel());
// getEquipment (alias for getEquipmentDetail)
when(mockEquipmentService.getEquipment(any))
.thenAnswer((_) async => MockDataHelpers.createMockEquipmentModel());
// getEquipmentHistory
when(mockEquipmentService.getEquipmentHistory(any, page: anyNamed('page'), perPage: anyNamed('perPage')))
.thenAnswer((_) async => []);
// Additional mock setups can be added here if needed
}
/// UserService Mock 설정
static void setupUserServiceMock(
MockUserService mockUserService, {
bool getUsersSuccess = true,
bool createUserSuccess = true,
bool updateUserSuccess = true,
bool deleteUserSuccess = true,
int userCount = 10,
}) {
// getUsers
if (getUsersSuccess) {
when(mockUserService.getUsers(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
role: anyNamed('role'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockUserModelList(count: userCount),
);
} else {
when(mockUserService.getUsers(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
role: anyNamed('role'),
)).thenThrow(Exception('사용자 목록 조회 실패'));
}
// createUser
if (createUserSuccess) {
when(mockUserService.createUser(
username: anyNamed('username'),
email: anyNamed('email'),
password: anyNamed('password'),
name: anyNamed('name'),
role: anyNamed('role'),
companyId: anyNamed('companyId'),
branchId: anyNamed('branchId'),
phone: anyNamed('phone'),
position: anyNamed('position'),
)).thenAnswer((_) async => MockDataHelpers.createMockUserModel());
} else {
when(mockUserService.createUser(
username: anyNamed('username'),
email: anyNamed('email'),
password: anyNamed('password'),
name: anyNamed('name'),
role: anyNamed('role'),
companyId: anyNamed('companyId'),
branchId: anyNamed('branchId'),
phone: anyNamed('phone'),
position: anyNamed('position'),
)).thenThrow(Exception('사용자 생성 실패'));
}
// updateUser
if (updateUserSuccess) {
when(mockUserService.updateUser(
any,
name: anyNamed('name'),
email: anyNamed('email'),
password: anyNamed('password'),
phone: anyNamed('phone'),
companyId: anyNamed('companyId'),
branchId: anyNamed('branchId'),
role: anyNamed('role'),
position: anyNamed('position'),
)).thenAnswer((_) async => MockDataHelpers.createMockUserModel());
} else {
when(mockUserService.updateUser(
any,
name: anyNamed('name'),
email: anyNamed('email'),
password: anyNamed('password'),
phone: anyNamed('phone'),
companyId: anyNamed('companyId'),
branchId: anyNamed('branchId'),
role: anyNamed('role'),
position: anyNamed('position'),
)).thenThrow(Exception('사용자 수정 실패'));
}
// deleteUser
if (deleteUserSuccess) {
when(mockUserService.deleteUser(any))
.thenAnswer((_) async {});
} else {
when(mockUserService.deleteUser(any))
.thenThrow(Exception('사용자 삭제 실패'));
}
// getUser
when(mockUserService.getUser(any))
.thenAnswer((_) async => MockDataHelpers.createMockUserModel());
// changePassword
when(mockUserService.changePassword(any, any, any))
.thenAnswer((_) async {});
// changeUserStatus
when(mockUserService.changeUserStatus(any, any))
.thenAnswer((_) async => MockDataHelpers.createMockUserModel());
// checkDuplicateUsername
when(mockUserService.checkDuplicateUsername(any))
.thenAnswer((_) async => false);
}
/// LicenseService Mock 설정
static void setupLicenseServiceMock(
MockLicenseService mockLicenseService, {
bool getLicensesSuccess = true,
bool createLicenseSuccess = true,
bool updateLicenseSuccess = true,
bool deleteLicenseSuccess = true,
int licenseCount = 10,
}) {
// getLicenses
if (getLicensesSuccess) {
when(mockLicenseService.getLicenses(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockLicenseModelList(count: licenseCount),
);
} else {
when(mockLicenseService.getLicenses(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenThrow(ServerFailure(
message: '라이선스 목록을 불러오는 데 실패했습니다',
));
}
// createLicense
if (createLicenseSuccess) {
when(mockLicenseService.createLicense(any))
.thenAnswer((_) async => MockDataHelpers.createMockLicenseModel());
} else {
when(mockLicenseService.createLicense(any))
.thenThrow(ServerFailure(
message: '라이선스 생성에 실패했습니다',
));
}
// updateLicense
if (updateLicenseSuccess) {
when(mockLicenseService.updateLicense(any))
.thenAnswer((_) async => MockDataHelpers.createMockLicenseModel());
} else {
when(mockLicenseService.updateLicense(any))
.thenThrow(ServerFailure(
message: '라이선스 수정에 실패했습니다',
));
}
// deleteLicense
if (deleteLicenseSuccess) {
when(mockLicenseService.deleteLicense(any))
.thenAnswer((_) async {});
} else {
when(mockLicenseService.deleteLicense(any))
.thenThrow(ServerFailure(
message: '라이선스 삭제에 실패했습니다',
));
}
// getLicenseById
when(mockLicenseService.getLicenseById(any))
.thenAnswer((_) async => MockDataHelpers.createMockLicenseModel());
// getExpiringLicenses
when(mockLicenseService.getExpiringLicenses(
days: anyNamed('days'),
page: anyNamed('page'),
perPage: anyNamed('perPage'),
)).thenAnswer((_) async => MockDataHelpers.createMockLicenseModelList(count: 3));
// assignLicense
when(mockLicenseService.assignLicense(any, any))
.thenAnswer((_) async => MockDataHelpers.createMockLicenseModel());
// unassignLicense
when(mockLicenseService.unassignLicense(any))
.thenAnswer((_) async => MockDataHelpers.createMockLicenseModel());
}
/// WarehouseService Mock 설정
static void setupWarehouseServiceMock(
MockWarehouseService mockWarehouseService, {
bool getWarehousesSuccess = true,
bool createWarehouseSuccess = true,
bool updateWarehouseSuccess = true,
bool deleteWarehouseSuccess = true,
int warehouseCount = 5,
}) {
// getWarehouses
if (getWarehousesSuccess) {
when(mockWarehouseService.getWarehouseLocations(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockWarehouseLocationList(count: warehouseCount),
);
} else {
when(mockWarehouseService.getWarehouseLocations(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
)).thenThrow(ServerFailure(
message: '창고 위치 목록을 불러오는 데 실패했습니다',
));
}
// createWarehouse
if (createWarehouseSuccess) {
when(mockWarehouseService.createWarehouseLocation(any))
.thenAnswer((_) async => MockDataHelpers.createMockWarehouseLocation());
} else {
when(mockWarehouseService.createWarehouseLocation(any))
.thenThrow(ServerFailure(
message: '창고 위치 생성에 실패했습니다',
));
}
// updateWarehouse
if (updateWarehouseSuccess) {
when(mockWarehouseService.updateWarehouseLocation(any))
.thenAnswer((_) async => MockDataHelpers.createMockWarehouseLocation());
} else {
when(mockWarehouseService.updateWarehouseLocation(any))
.thenThrow(ServerFailure(
message: '창고 위치 수정에 실패했습니다',
));
}
// deleteWarehouse
if (deleteWarehouseSuccess) {
when(mockWarehouseService.deleteWarehouseLocation(any))
.thenAnswer((_) async {});
} else {
when(mockWarehouseService.deleteWarehouseLocation(any))
.thenThrow(ServerFailure(
message: '창고 위치 삭제에 실패했습니다',
));
}
// getWarehouseLocationById
when(mockWarehouseService.getWarehouseLocationById(any))
.thenAnswer((_) async => MockDataHelpers.createMockWarehouseLocation());
// getWarehouseEquipment
when(mockWarehouseService.getWarehouseEquipment(
any,
page: anyNamed('page'),
perPage: anyNamed('perPage'),
)).thenAnswer((_) async => []);
// getWarehouseCapacity
when(mockWarehouseService.getWarehouseCapacity(any))
.thenAnswer((_) async => MockDataHelpers.createMockWarehouseCapacityInfo());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:superport/services/auth_service.dart';
import 'package:superport/services/company_service.dart';
import 'package:superport/services/equipment_service.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/services/user_service.dart';
import 'package:superport/data/models/auth/auth_user.dart';
import 'package:superport/models/user_model.dart';
import 'mock_data_helpers.dart';
import 'simple_mock_services.mocks.dart';
// Mockito 어노테이션으로 Mock 클래스 생성
@GenerateMocks([
AuthService,
CompanyService,
MockDataService,
EquipmentService,
UserService,
])
void main() {}
/// 간단한 Mock 서비스 설정 헬퍼
class SimpleMockServiceHelpers {
/// AuthService Mock 설정
static void setupAuthServiceMock(
MockAuthService mockAuthService, {
bool isLoggedIn = false,
}) {
// isLoggedIn
when(mockAuthService.isLoggedIn())
.thenAnswer((_) async => isLoggedIn);
// getCurrentUser
when(mockAuthService.getCurrentUser())
.thenAnswer((_) async => isLoggedIn ? AuthUser(
id: 1,
username: 'test_user',
name: '테스트 사용자',
email: 'test@example.com',
role: 'admin',
) : null);
}
/// CompanyService Mock 설정
static void setupCompanyServiceMock(
MockCompanyService mockCompanyService, {
bool getCompaniesSuccess = true,
bool deleteCompanySuccess = true,
int companyCount = 10,
}) {
// getCompanies
if (getCompaniesSuccess) {
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockCompanyList(count: companyCount),
);
} else {
when(mockCompanyService.getCompanies(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
search: anyNamed('search'),
isActive: anyNamed('isActive'),
)).thenThrow(
Exception('회사 목록을 불러오는 중 오류가 발생했습니다.'),
);
}
// deleteCompany
if (deleteCompanySuccess) {
when(mockCompanyService.deleteCompany(any))
.thenAnswer((_) async {});
} else {
when(mockCompanyService.deleteCompany(any))
.thenThrow(
Exception('회사 삭제 중 오류가 발생했습니다.'),
);
}
}
/// MockDataService Mock 설정
static void setupMockDataServiceMock(
MockMockDataService mockDataService, {
int companyCount = 10,
int userCount = 10,
}) {
when(mockDataService.getAllCompanies()).thenReturn(
MockDataHelpers.createMockCompanyList(count: companyCount)
);
when(mockDataService.deleteCompany(any)).thenReturn(null);
when(mockDataService.getAllUsers()).thenReturn(
MockDataHelpers.createMockUserModelList(count: userCount)
);
when(mockDataService.deleteUser(any)).thenReturn(null);
when(mockDataService.getCompanyById(any)).thenAnswer((invocation) {
final id = invocation.positionalArguments[0] as int;
final companies = MockDataHelpers.createMockCompanyList(count: companyCount);
return companies.firstWhere(
(c) => c.id == id,
orElse: () => MockDataHelpers.createMockCompany(id: id),
);
});
}
/// UserService Mock 설정
static void setupUserServiceMock(
MockUserService mockUserService, {
bool getUsersSuccess = true,
bool deleteUserSuccess = true,
bool changeUserStatusSuccess = true,
int userCount = 10,
}) {
// getUsers
if (getUsersSuccess) {
when(mockUserService.getUsers(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
role: anyNamed('role'),
)).thenAnswer((_) async =>
MockDataHelpers.createMockUserModelList(count: userCount),
);
} else {
when(mockUserService.getUsers(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
role: anyNamed('role'),
)).thenThrow(
Exception('사용자 목록을 불러오는 중 오류가 발생했습니다.'),
);
}
// deleteUser
if (deleteUserSuccess) {
when(mockUserService.deleteUser(any))
.thenAnswer((_) async {});
} else {
when(mockUserService.deleteUser(any))
.thenThrow(
Exception('사용자 삭제 중 오류가 발생했습니다.'),
);
}
// changeUserStatus
if (changeUserStatusSuccess) {
when(mockUserService.changeUserStatus(any, any))
.thenAnswer((invocation) async {
final id = invocation.positionalArguments[0] as int;
final isActive = invocation.positionalArguments[1] as bool;
return MockDataHelpers.createMockUserModel(
id: id,
isActive: isActive,
);
});
} else {
when(mockUserService.changeUserStatus(any, any))
.thenThrow(
Exception('사용자 상태 변경 중 오류가 발생했습니다.'),
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,296 @@
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();
}