test: 통합 테스트 오류 및 경고 수정
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- 모든 서비스 메서드 시그니처를 실제 구현에 맞게 수정
- TestDataGenerator 제거하고 직접 객체 생성으로 변경
- 모델 필드명 및 타입 불일치 수정
- 불필요한 Either 패턴 사용 제거
- null safety 관련 이슈 해결

수정된 파일:
- test/integration/screens/company_integration_test.dart
- test/integration/screens/equipment_integration_test.dart
- test/integration/screens/user_integration_test.dart
- test/integration/screens/login_integration_test.dart
This commit is contained in:
JiWoong Sul
2025-08-05 20:24:05 +09:00
parent d6f34c0a52
commit 198aac6525
145 changed files with 41527 additions and 5220 deletions

View File

@@ -5,7 +5,6 @@ import 'package:superport/screens/equipment/controllers/equipment_list_controlle
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';

View File

@@ -0,0 +1,549 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
import 'package:superport/screens/license/controllers/license_list_controller.dart';
import 'package:superport/services/license_service.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/models/license_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 LicenseListController controller;
late MockLicenseService mockLicenseService;
late MockMockDataService mockDataService;
late GetIt getIt;
setUp(() {
getIt = setupTestGetIt();
mockLicenseService = MockLicenseService();
mockDataService = MockMockDataService();
});
group('LicenseListController API 모드 테스트', () {
setUp(() {
// GetIt에 서비스 먼저 등록
getIt.registerSingleton<LicenseService>(mockLicenseService);
// 등록 확인
expect(GetIt.instance.isRegistered<LicenseService>(), true);
// 컨트롤러 생성
controller = LicenseListController(
useApi: true,
mockDataService: mockDataService, // 검색 필터링을 위해 필요
);
});
tearDown(() {
controller.dispose();
getIt.reset();
});
test('초기 상태 확인', () {
expect(controller.licenses, isEmpty);
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.currentPage, 1);
expect(controller.hasMore, true);
expect(controller.total, 0);
});
test('라이선스 목록 로드 성공', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 5);
when(mockLicenseService.getLicenses(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => mockLicenses);
when(mockLicenseService.getTotalLicenses(
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => 5);
// Act
await controller.loadData();
// Assert
expect(controller.licenses, hasLength(5));
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.total, 5);
});
test('라이선스 목록 로드 실패', () async {
// Arrange
when(mockLicenseService.getLicenses(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenThrow(Exception('라이선스 목록을 불러오는 중 오류가 발생했습니다.'));
// Act
await controller.loadData();
// Assert
expect(controller.licenses, isEmpty);
expect(controller.isLoading, false);
expect(controller.error, contains('라이선스 목록을 불러오는 중 오류가 발생했습니다'));
});
test('검색 기능 테스트', () async {
// Arrange
final mockLicenses = [
MockDataHelpers.createMockLicenseModel(id: 1, productName: '라이선스 1'),
MockDataHelpers.createMockLicenseModel(id: 2, productName: '라이선스 2'),
MockDataHelpers.createMockLicenseModel(id: 3, productName: '다른 제품'),
MockDataHelpers.createMockLicenseModel(id: 4, productName: '라이선스 4'),
MockDataHelpers.createMockLicenseModel(id: 5, productName: '또 다른 제품'),
];
when(mockLicenseService.getLicenses(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => mockLicenses);
when(mockLicenseService.getTotalLicenses(
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => 5);
await controller.loadData();
expect(controller.licenses, hasLength(5));
// Act
controller.search('라이선스');
// API 모드에서는 디바운싱 300ms 대기 후 데이터 재로드 완료까지 대기
await Future.delayed(const Duration(milliseconds: 500));
// Assert - 클라이언트 사이드 필터링 확인
expect(controller.searchQuery, '라이선스');
// 원본 데이터 5개 중 '라이선스'를 포함하는 것은 3개
final filteredLicenses = controller.licenses.where((l) => l.productName!.contains('라이선스')).toList();
expect(filteredLicenses, hasLength(3));
});
test('필터 설정 테스트', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 3);
when(mockLicenseService.getLicenses(
page: anyNamed('page'),
perPage: anyNamed('perPage'),
isActive: true,
companyId: 1,
assignedUserId: anyNamed('assignedUserId'),
licenseType: 'SOFTWARE',
)).thenAnswer((_) async => mockLicenses);
when(mockLicenseService.getTotalLicenses(
isActive: true,
companyId: 1,
licenseType: 'SOFTWARE',
)).thenAnswer((_) async => 3);
// Act
controller.setFilters(
companyId: 1,
isActive: true,
licenseType: 'SOFTWARE',
);
await Future.delayed(const Duration(milliseconds: 100));
// Assert
expect(controller.selectedCompanyId, 1);
expect(controller.isActive, true);
expect(controller.licenseType, 'SOFTWARE');
verify(mockLicenseService.getLicenses(
page: 1,
perPage: 20,
isActive: true,
companyId: 1,
assignedUserId: null,
licenseType: 'SOFTWARE',
)).called(1);
});
test('필터 초기화 테스트', () async {
// Arrange
controller.setFilters(
companyId: 1,
isActive: true,
licenseType: 'SOFTWARE',
);
// Act
controller.clearFilters();
await Future.delayed(const Duration(milliseconds: 100));
// Assert
expect(controller.selectedCompanyId, isNull);
expect(controller.isActive, isNull);
expect(controller.licenseType, isNull);
expect(controller.searchQuery, isEmpty);
});
test('라이선스 삭제 성공', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 3);
when(mockLicenseService.getLicenses(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => mockLicenses);
when(mockLicenseService.getTotalLicenses(
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => 3);
when(mockLicenseService.deleteLicense(1))
.thenAnswer((_) async {});
await controller.loadData();
final initialTotal = controller.total;
// Act
await controller.deleteLicense(1);
// Assert
expect(controller.licenses.any((l) => l.id == 1), false);
expect(controller.total, initialTotal - 1);
});
test('라이선스 삭제 실패', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 3);
when(mockLicenseService.getLicenses(
page: 1,
perPage: 20,
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => mockLicenses);
when(mockLicenseService.getTotalLicenses(
isActive: null,
companyId: null,
assignedUserId: null,
licenseType: null,
)).thenAnswer((_) async => 3);
await controller.loadData();
final initialCount = controller.licenses.length;
expect(initialCount, 3);
when(mockLicenseService.deleteLicense(1))
.thenThrow(Exception('라이선스 삭제 중 오류가 발생했습니다'));
when(mockDataService.deleteLicense(1))
.thenThrow(Exception('라이선스 삭제 중 오류가 발생했습니다'));
// Act
await controller.deleteLicense(1);
// Assert - 삭제가 실패하면 목록은 변경되지 않아야 함
expect(controller.licenses.length, initialCount);
expect(controller.error, contains('라이선스 삭제 중 오류가 발생했습니다'));
// ID 1인 라이선스는 여전히 존재해야 함
expect(controller.licenses.any((l) => l.id == 1), true);
});
test('만료 예정 라이선스 조회', () async {
// Arrange
final now = DateTime.now();
final expiringMockLicenses = MockDataHelpers.createMockLicenseModelList(count: 3)
.map((license) => License(
id: license.id,
licenseKey: license.licenseKey,
productName: license.productName,
vendor: license.vendor,
licenseType: license.licenseType,
userCount: license.userCount,
purchaseDate: license.purchaseDate,
expiryDate: now.add(const Duration(days: 15)), // 15일 후 만료
purchasePrice: license.purchasePrice,
companyId: license.companyId,
isActive: license.isActive,
))
.toList();
when(mockLicenseService.getExpiringLicenses(days: 30))
.thenAnswer((_) async => expiringMockLicenses);
// Act
final expiringLicenses = await controller.getExpiringLicenses(days: 30);
// Assert
expect(expiringLicenses, hasLength(3));
expect(expiringLicenses.every((l) => l.expiryDate != null), true);
verify(mockLicenseService.getExpiringLicenses(days: 30)).called(1);
});
test('라이선스 상태별 개수 조회', () async {
// Arrange - anyNamed 사용하여 모든 매개변수 허용
when(mockLicenseService.getTotalLicenses(
isActive: true,
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenAnswer((_) async => 10);
when(mockLicenseService.getTotalLicenses(
isActive: false,
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenAnswer((_) async => 5);
// 만료 예정 라이선스 Mock
final now = DateTime.now();
final expiringMockLicenses = MockDataHelpers.createMockLicenseModelList(count: 3)
.map((license) => License(
id: license.id,
licenseKey: license.licenseKey,
productName: license.productName,
vendor: license.vendor,
licenseType: license.licenseType,
userCount: license.userCount,
purchaseDate: license.purchaseDate,
expiryDate: now.add(const Duration(days: 15)), // 15일 후 만료
purchasePrice: license.purchasePrice,
companyId: license.companyId,
isActive: license.isActive,
))
.toList();
when(mockLicenseService.getExpiringLicenses(days: 30))
.thenAnswer((_) async => expiringMockLicenses);
// Mock 데이터 서비스의 getAllLicenses도 설정
when(mockDataService.getAllLicenses()).thenReturn(expiringMockLicenses);
// Act
final counts = await controller.getLicenseStatusCounts();
// Assert
expect(counts['active'], 10);
expect(counts['inactive'], 5);
expect(counts['total'], 15);
expect(counts['expiring'], 3);
});
test('다음 페이지 로드', () async {
// Arrange
final firstPageLicenses = MockDataHelpers.createMockLicenseModelList(count: 20);
final secondPageLicenses = MockDataHelpers.createMockLicenseModelList(count: 20)
.map((l) => License(
id: l.id! + 20,
licenseKey: 'KEY-NEXT-${l.id! + 20}',
productName: '다음 페이지 라이선스 ${l.id! + 20}',
vendor: l.vendor,
licenseType: l.licenseType,
companyId: l.companyId,
isActive: l.isActive,
))
.toList();
when(mockLicenseService.getLicenses(
page: 1,
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenAnswer((_) async => firstPageLicenses);
when(mockLicenseService.getLicenses(
page: 2,
perPage: anyNamed('perPage'),
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenAnswer((_) async => secondPageLicenses);
when(mockLicenseService.getTotalLicenses(
isActive: anyNamed('isActive'),
companyId: anyNamed('companyId'),
assignedUserId: anyNamed('assignedUserId'),
licenseType: anyNamed('licenseType'),
)).thenAnswer((_) async => 40);
// Mock 데이터 서비스도 설정
final allLicenses = [...firstPageLicenses, ...secondPageLicenses];
when(mockDataService.getAllLicenses()).thenReturn(allLicenses);
// Act
await controller.loadData();
expect(controller.licenses, hasLength(20));
expect(controller.currentPage, 1);
expect(controller.hasMore, true);
await controller.loadNextPage();
// Assert
expect(controller.currentPage, 2);
expect(controller.licenses, hasLength(40));
// 첫 번째 페이지의 마짉 라이선스와 두 번째 페이지의 첫 번째 라이선스 확인
expect(controller.licenses[19].id, 20);
expect(controller.licenses[20].id, 21);
});
});
group('LicenseListController Mock 모드 테스트', () {
setUp(() {
// Mock 데이터 설정
SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService, licenseCount: 10);
controller = LicenseListController(
useApi: false,
mockDataService: mockDataService,
);
});
tearDown(() {
controller.dispose();
});
test('Mock 데이터로 라이선스 목록 로드', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 15);
when(mockDataService.getAllLicenses()).thenReturn(mockLicenses);
// Act
await controller.loadData();
// Assert
expect(controller.licenses.length, lessThanOrEqualTo(20)); // pageSize는 20
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.total, 15);
});
test('Mock 모드에서 검색 (즉시 실행)', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 5);
when(mockDataService.getAllLicenses()).thenReturn(mockLicenses);
await controller.loadData();
// Act
controller.search('라이선스 1');
// Assert - Mock 모드에서는 즉시 필터링됨
expect(controller.licenses.every((l) =>
l.productName!.toLowerCase().contains('라이선스 1')), true);
});
test('Mock 모드에서 필터링', () async {
// Arrange
final mockLicenses = [
MockDataHelpers.createMockLicenseModel(id: 1, companyId: 1),
MockDataHelpers.createMockLicenseModel(id: 2, companyId: 1),
MockDataHelpers.createMockLicenseModel(id: 3, companyId: 2),
MockDataHelpers.createMockLicenseModel(id: 4, companyId: 2),
MockDataHelpers.createMockLicenseModel(id: 5, companyId: 3),
];
when(mockDataService.getAllLicenses()).thenReturn(mockLicenses);
// Act
controller.setFilters(companyId: 1);
await Future.delayed(const Duration(milliseconds: 100));
// Assert
expect(controller.licenses.every((l) => l.companyId == 1), true);
expect(controller.total, 2);
});
test('Mock 모드에서 라이선스 삭제', () async {
// Arrange
final mockLicenses = MockDataHelpers.createMockLicenseModelList(count: 3);
when(mockDataService.getAllLicenses()).thenReturn(mockLicenses);
when(mockDataService.deleteLicense(any)).thenReturn(null);
await controller.loadData();
final initialCount = controller.licenses.length;
// Act
await controller.deleteLicense(1);
// Assert
expect(controller.licenses.length, initialCount - 1);
expect(controller.licenses.any((l) => l.id == 1), false);
verify(mockDataService.deleteLicense(1)).called(1);
});
test('Mock 모드에서 상태별 개수 조회', () async {
// Arrange
final now = DateTime.now();
final mockLicenses = [
MockDataHelpers.createMockLicenseModel(
id: 1,
isActive: true,
expiryDate: now.add(const Duration(days: 365)),
),
MockDataHelpers.createMockLicenseModel(
id: 2,
isActive: true,
expiryDate: now.add(const Duration(days: 15)), // 만료 예정
),
MockDataHelpers.createMockLicenseModel(
id: 3,
isActive: true,
expiryDate: now.subtract(const Duration(days: 10)), // 만료됨
),
MockDataHelpers.createMockLicenseModel(
id: 4,
isActive: false,
),
MockDataHelpers.createMockLicenseModel(
id: 5,
isActive: false,
),
];
when(mockDataService.getAllLicenses()).thenReturn(mockLicenses);
// Act
final counts = await controller.getLicenseStatusCounts();
// Assert
expect(counts['active'], 3);
expect(counts['inactive'], 2);
expect(counts['expiring'], 1);
expect(counts['expired'], 1);
expect(counts['total'], 5);
});
});
}

View File

@@ -0,0 +1,247 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:superport/screens/overview/controllers/overview_controller.dart';
import 'package:superport/services/dashboard_service.dart';
import '../../helpers/test_helpers.dart';
import '../../helpers/simple_mock_services.dart';
import '../../helpers/simple_mock_services.mocks.dart';
void main() {
late OverviewController controller;
late MockDashboardService mockDashboardService;
late GetIt getIt;
setUp(() {
getIt = setupTestGetIt();
mockDashboardService = MockDashboardService();
// GetIt에 서비스 등록
getIt.registerSingleton<DashboardService>(mockDashboardService);
// Mock 설정
SimpleMockServiceHelpers.setupDashboardServiceMock(mockDashboardService);
controller = OverviewController();
});
tearDown(() {
controller.dispose();
getIt.reset();
});
group('OverviewController 테스트', () {
test('초기 상태 확인', () {
expect(controller.overviewStats, isNull);
expect(controller.recentActivities, isEmpty);
expect(controller.equipmentStatus, isNull);
expect(controller.expiringLicenses, isEmpty);
expect(controller.isLoading, isFalse);
expect(controller.error, isNull);
expect(controller.totalCompanies, equals(0));
expect(controller.totalUsers, equals(0));
});
group('대시보드 데이터 로드', () {
test('데이터 로드 성공', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: true,
getRecentActivitiesSuccess: true,
getEquipmentStatusSuccess: true,
getExpiringLicensesSuccess: true,
);
// when
await controller.loadData();
// then
expect(controller.overviewStats, isNotNull);
expect(controller.overviewStats!.totalCompanies, equals(50));
expect(controller.overviewStats!.totalUsers, equals(200));
expect(controller.recentActivities, isNotEmpty);
expect(controller.equipmentStatus, isNotNull);
expect(controller.equipmentStatus!.available, equals(350));
expect(controller.expiringLicenses, isNotEmpty);
expect(controller.isLoading, isFalse);
expect(controller.error, isNull);
expect(controller.totalCompanies, equals(50));
expect(controller.totalUsers, equals(200));
});
test('loadDashboardData가 loadData를 호출하는지 확인', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: true,
);
// when
await controller.loadDashboardData();
// then
expect(controller.overviewStats, isNotNull);
});
});
group('개별 데이터 로드 오류 처리', () {
test('대시보드 통계 로드 실패', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: false,
getRecentActivitiesSuccess: true,
getEquipmentStatusSuccess: true,
getExpiringLicensesSuccess: true,
);
// when
await controller.loadData();
// then
expect(controller.overviewStats, isNull);
expect(controller.recentActivities, isNotEmpty);
expect(controller.equipmentStatus, isNotNull);
expect(controller.expiringLicenses, isNotEmpty);
expect(controller.error, contains('대시보드 통계를 불러오는 중 오류가 발생했습니다.'));
});
test('최근 활동 로드 실패', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: true,
getRecentActivitiesSuccess: false,
getEquipmentStatusSuccess: true,
getExpiringLicensesSuccess: true,
);
// when
await controller.loadData();
// then
expect(controller.overviewStats, isNotNull);
expect(controller.recentActivities, isEmpty);
expect(controller.equipmentStatus, isNotNull);
expect(controller.expiringLicenses, isNotEmpty);
expect(controller.error, contains('최근 활동을 불러오는 중 오류가 발생했습니다.'));
});
test('장비 상태 분포 로드 실패', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: true,
getRecentActivitiesSuccess: true,
getEquipmentStatusSuccess: false,
getExpiringLicensesSuccess: true,
);
// when
await controller.loadData();
// then
expect(controller.overviewStats, isNotNull);
expect(controller.recentActivities, isNotEmpty);
expect(controller.equipmentStatus, isNull);
expect(controller.expiringLicenses, isNotEmpty);
expect(controller.error, contains('장비 상태 분포를 불러오는 중 오류가 발생했습니다.'));
});
test('만료 예정 라이선스 로드 실패', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: true,
getRecentActivitiesSuccess: true,
getEquipmentStatusSuccess: true,
getExpiringLicensesSuccess: false,
);
// when
await controller.loadData();
// then
expect(controller.overviewStats, isNotNull);
expect(controller.recentActivities, isNotEmpty);
expect(controller.equipmentStatus, isNotNull);
expect(controller.expiringLicenses, isEmpty);
expect(controller.error, contains('만료 예정 라이선스를 불러오는 중 오류가 발생했습니다.'));
});
});
group('활동 타입별 아이콘 및 색상', () {
test('활동 타입별 아이콘 확인', () {
expect(controller.getActivityIcon('equipment_in'), equals(Icons.input));
expect(controller.getActivityIcon('장비 입고'), equals(Icons.input));
expect(controller.getActivityIcon('equipment_out'), equals(Icons.output));
expect(controller.getActivityIcon('장비 출고'), equals(Icons.output));
expect(controller.getActivityIcon('user_create'), equals(Icons.person_add));
expect(controller.getActivityIcon('사용자 추가'), equals(Icons.person_add));
expect(controller.getActivityIcon('license_create'), equals(Icons.vpn_key));
expect(controller.getActivityIcon('라이선스 등록'), equals(Icons.vpn_key));
expect(controller.getActivityIcon('unknown'), equals(Icons.notifications));
});
test('활동 타입별 색상 확인', () {
// 색상 값은 실제 AppThemeTailwind 값에 따라 다를 수 있으므로
// null이 아닌지만 확인
expect(controller.getActivityColor('equipment_in'), isNotNull);
expect(controller.getActivityColor('장비 입고'), isNotNull);
expect(controller.getActivityColor('equipment_out'), isNotNull);
expect(controller.getActivityColor('장비 출고'), isNotNull);
expect(controller.getActivityColor('user_create'), isNotNull);
expect(controller.getActivityColor('사용자 추가'), isNotNull);
expect(controller.getActivityColor('license_create'), isNotNull);
expect(controller.getActivityColor('라이선스 등록'), isNotNull);
expect(controller.getActivityColor('unknown'), isNotNull);
});
});
group('로딩 상태 관리', () {
test('로드 중 isLoading이 true가 되는지 확인', () async {
// given
bool loadingStateChanged = false;
controller.addListener(() {
if (controller.isLoading) {
loadingStateChanged = true;
}
});
// when
final loadFuture = controller.loadData();
// 잠시 대기하여 로딩 상태가 변경될 시간을 줌
await Future.delayed(const Duration(milliseconds: 10));
// then
expect(loadingStateChanged, isTrue);
// 로드 완료 대기
await loadFuture;
expect(controller.isLoading, isFalse);
});
});
test('모든 데이터 로드 실패 시 첫 번째 에러만 표시', () async {
// given
SimpleMockServiceHelpers.setupDashboardServiceMock(
mockDashboardService,
getOverviewStatsSuccess: false,
getRecentActivitiesSuccess: false,
getEquipmentStatusSuccess: false,
getExpiringLicensesSuccess: false,
);
// when
await controller.loadData();
// then
// error getter는 첫 번째 null이 아닌 에러를 반환
expect(controller.error, isNotNull);
expect(controller.error, contains('오류가 발생했습니다'));
});
});
}

View File

@@ -0,0 +1,391 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
import 'package:superport/screens/warehouse_location/controllers/warehouse_location_list_controller.dart';
import 'package:superport/services/warehouse_service.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/models/warehouse_location_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() {
group('WarehouseLocationListController API 모드 테스트', () {
late WarehouseLocationListController controller;
late MockWarehouseService mockWarehouseService;
late MockMockDataService mockDataService;
setUp(() {
// GetIt 초기화
GetIt.instance.reset();
mockWarehouseService = MockWarehouseService();
mockDataService = MockMockDataService();
// GetIt에 서비스 등록
GetIt.instance.registerSingleton<WarehouseService>(mockWarehouseService);
// Mock 설정
SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService, warehouseCount: 10);
SimpleMockServiceHelpers.setupWarehouseServiceMock(mockWarehouseService);
});
tearDown(() {
controller?.dispose();
GetIt.instance.reset();
});
test('초기 상태 확인', () {
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
expect(controller.warehouseLocations, isEmpty);
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.currentPage, 1);
expect(controller.hasMore, true);
expect(controller.total, 0);
});
test('창고 위치 목록 로드 성공', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 5);
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: null,
)).thenAnswer((_) async => mockLocations);
when(mockWarehouseService.getTotalWarehouseLocations(
isActive: null,
)).thenAnswer((_) async => 5);
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
// Act
await controller.loadWarehouseLocations();
// Assert
expect(controller.warehouseLocations, hasLength(5));
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.total, 5);
});
test('창고 위치 목록 로드 실패', () async {
// Arrange
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: null,
)).thenThrow(Exception('창고 위치 목록을 불러오는 중 오류가 발생했습니다.'));
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
// Act
await controller.loadWarehouseLocations();
// Assert
expect(controller.warehouseLocations, isEmpty);
expect(controller.isLoading, false);
expect(controller.error, contains('창고 위치 목록을 불러오는 중 오류가 발생했습니다'));
});
test('검색 기능 테스트', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 5);
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: null,
)).thenAnswer((_) async => mockLocations);
when(mockWarehouseService.getTotalWarehouseLocations(
isActive: null,
)).thenAnswer((_) async => 5);
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
await controller.loadWarehouseLocations();
// Act
controller.search('창고 1');
// Assert
expect(controller.searchQuery, '창고 1');
// '창고 1'을 검색하면 '창고 1'이 포함된 항목만 표시되어야 함
expect(controller.warehouseLocations.any((l) =>
l.name.contains('창고 1')), true);
expect(controller.warehouseLocations.length, greaterThan(0));
});
test('필터 설정 테스트', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 3);
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: true,
)).thenAnswer((_) async => mockLocations);
when(mockWarehouseService.getTotalWarehouseLocations(
isActive: true,
)).thenAnswer((_) async => 3);
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
// Act
controller.setFilters(isActive: true);
await Future.delayed(const Duration(milliseconds: 100));
// Assert
expect(controller.isActive, true);
verify(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: true,
)).called(1);
});
test('필터 초기화 테스트', () async {
// Arrange
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
controller.setFilters(isActive: true);
// Act
controller.clearFilters();
await Future.delayed(const Duration(milliseconds: 100));
// Assert
expect(controller.isActive, isNull);
expect(controller.searchQuery, isEmpty);
});
test('창고 위치 삭제 성공', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 3);
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: null,
)).thenAnswer((_) async => mockLocations);
when(mockWarehouseService.getTotalWarehouseLocations(
isActive: null,
)).thenAnswer((_) async => 3);
when(mockWarehouseService.deleteWarehouseLocation(1))
.thenAnswer((_) async {});
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
await controller.loadWarehouseLocations();
final initialTotal = controller.total;
// Act
await controller.deleteWarehouseLocation(1);
// Assert
expect(controller.warehouseLocations.any((l) => l.id == 1), false);
expect(controller.total, initialTotal - 1);
verify(mockWarehouseService.deleteWarehouseLocation(1)).called(1);
});
test('창고 위치 삭제 실패', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 3);
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: null,
)).thenAnswer((_) async => mockLocations);
when(mockWarehouseService.getTotalWarehouseLocations(
isActive: null,
)).thenAnswer((_) async => 3);
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
await controller.loadWarehouseLocations();
final initialCount = controller.warehouseLocations.length;
when(mockWarehouseService.deleteWarehouseLocation(any))
.thenThrow(Exception('창고 위치 삭제 중 오류가 발생했습니다.'));
// Act
await controller.deleteWarehouseLocation(1);
// Assert
expect(controller.error, contains('Exception: 창고 위치 삭제 중 오류가 발생했습니다'));
expect(controller.warehouseLocations.length, initialCount); // 삭제되지 않음
});
test('다음 페이지 로드', () async {
// Arrange
final firstPageLocations = MockDataHelpers.createMockWarehouseLocationList(count: 20);
final firstPageCount = firstPageLocations.length;
final secondPageLocations = MockDataHelpers.createMockWarehouseLocationList(count: 10)
.map((l) => WarehouseLocation(
id: l.id + 20,
name: '다음 페이지 창고 ${l.id}',
address: l.address,
remark: l.remark,
))
.toList();
final secondPageCount = secondPageLocations.length;
when(mockWarehouseService.getWarehouseLocations(
page: 1,
perPage: 20,
isActive: null,
)).thenAnswer((_) async => firstPageLocations);
when(mockWarehouseService.getWarehouseLocations(
page: 2,
perPage: 20,
isActive: null,
)).thenAnswer((_) async => secondPageLocations);
when(mockWarehouseService.getTotalWarehouseLocations(
isActive: null,
)).thenAnswer((_) async => 30);
controller = WarehouseLocationListController(
useApi: true,
mockDataService: mockDataService,
);
// Act
await controller.loadWarehouseLocations();
expect(controller.warehouseLocations, hasLength(firstPageLocations.length));
await controller.loadNextPage();
// Assert
expect(controller.warehouseLocations, hasLength(firstPageCount + secondPageCount));
expect(controller.currentPage, 2);
});
});
group('WarehouseLocationListController Mock 모드 테스트', () {
late WarehouseLocationListController controller;
late MockMockDataService mockDataService;
setUp(() {
// GetIt 초기화
GetIt.instance.reset();
mockDataService = MockMockDataService();
// Mock 설정
SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService, warehouseCount: 10);
controller = WarehouseLocationListController(
useApi: false,
mockDataService: mockDataService,
);
});
tearDown(() {
controller.dispose();
GetIt.instance.reset();
});
test('Mock 데이터로 창고 위치 목록 로드', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 15);
when(mockDataService.getAllWarehouseLocations()).thenReturn(mockLocations);
// Act
await controller.loadWarehouseLocations();
// Assert
expect(controller.warehouseLocations.length, lessThanOrEqualTo(20)); // pageSize는 20
expect(controller.isLoading, false);
expect(controller.error, isNull);
expect(controller.total, 15);
});
test('Mock 모드에서 검색', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 5);
when(mockDataService.getAllWarehouseLocations()).thenReturn(mockLocations);
await controller.loadWarehouseLocations();
// Act
controller.search('창고 1');
// Assert
expect(controller.warehouseLocations.every((l) =>
l.name.toLowerCase().contains('창고 1')), true);
});
test('Mock 모드에서 필터링', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 10);
when(mockDataService.getAllWarehouseLocations()).thenReturn(mockLocations);
// Act
controller.setFilters(isActive: true);
await Future.delayed(const Duration(milliseconds: 100));
// Assert
expect(controller.isActive, true);
// Mock 데이터에는 isActive 필드가 없으므로 모든 데이터가 활성으로 처리됨
expect(controller.warehouseLocations, hasLength(10));
});
test('Mock 모드에서 창고 위치 삭제', () async {
// Arrange
final mockLocations = MockDataHelpers.createMockWarehouseLocationList(count: 3);
when(mockDataService.getAllWarehouseLocations()).thenReturn(mockLocations);
when(mockDataService.deleteWarehouseLocation(any)).thenReturn(null);
await controller.loadWarehouseLocations();
final initialCount = controller.warehouseLocations.length;
// Act
await controller.deleteWarehouseLocation(1);
// Assert
expect(controller.warehouseLocations.length, initialCount - 1);
expect(controller.warehouseLocations.any((l) => l.id == 1), false);
verify(mockDataService.deleteWarehouseLocation(1)).called(1);
});
});
}