이벤트 가져오기 위젯 테스트 추가 및 테스트 훅 도입

This commit is contained in:
2025-09-12 06:33:44 +09:00
parent 107abc963f
commit a5f2544d11
121 changed files with 39277 additions and 3911 deletions
+129 -13
View File
@@ -4,10 +4,9 @@ import 'package:mockito/annotations.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:lanebow/services/score_service.dart';
import 'package:lanebow/services/api_service.dart';
import 'package:lanebow/services/event_bus.dart';
import 'package:lanebow/models/score_model.dart';
import 'package:lanebow/config/api_config.dart';
import 'package:lanebow/utils/test_utils.dart';
import '../utils/event_bus_test_utils.dart';
// 모킹 클래스 생성
@GenerateMocks([ApiService])
@@ -52,10 +51,7 @@ void main() {
// 테스트별 고유 ID 생성 및 설정
final testId = 'score_service_test_${DateTime.now().millisecondsSinceEpoch}';
EventBus.setCurrentTestId(testId);
// EventBus 완전 초기화
EventBus().reset();
await EventBusTestUtils.setUp(testId);
// SharedPreferences 모킹
SharedPreferences.setMockInitialValues({
@@ -72,15 +68,12 @@ void main() {
scoreService.initialize(testToken);
});
tearDown(() {
// 테스트 종료 후 EventBus 초기화
EventBus().reset();
tearDown(() async {
// 테스트에서 생성된 ScoreService 정리
scoreService.dispose();
try { scoreService.dispose(); } catch (_) {}
// 테스트 ID 초기화
EventBus.clearCurrentTestId();
// EventBus 표준 정리
await EventBusTestUtils.tearDown();
// 테스트 모드 해제
TestUtils.setTestMode(false);
@@ -130,6 +123,8 @@ void main() {
expect(scoreService.scores.length, 1);
expect(scoreService.scores[0].id, testScoreId);
expect(scoreService.scores[0].totalScore, 55);
expect(scoreService.lastLoadedAt, isNotNull);
expect(scoreService.lastError, isNull);
});
test('fetchClubScores 메서드가 객체 형태의 응답도 처리해야 함', () async {
@@ -279,6 +274,125 @@ void main() {
expect(scoreService.scores.length, 0);
});
test('fetchClubScores: clubId가 null이면 예외를 던지고 isLoading=false 유지', () async {
// given: prefs에서 clubId 제거
SharedPreferences.setMockInitialValues({});
// 새 서비스 인스턴스(클럽 미설정)
final svc = ScoreService.forTest(mockApiService);
svc.initialize(testToken);
// when/then
expect(() async => await svc.fetchClubScores(), throwsA(isA<Exception>()));
// 약간 대기 후 상태 확인(비동기 반영 안정화)
await EventBusTestUtils.waitForIdle();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(svc.isLoading, isFalse);
expect(svc.lastError, isNotNull);
});
test('fetchClubScores: 빈 목록 응답이면 기존 scores를 비워야 함', () async {
// given: 먼저 1개 로드
when(mockApiService.post(
'${ApiConfig.clubs}/scores',
data: {'clubId': testClubId},
)).thenAnswer((_) async => [testScore]);
await scoreService.fetchClubScores();
expect(scoreService.scores.length, 1);
// 빈 목록 응답으로 재설정
when(mockApiService.post(
'${ApiConfig.clubs}/scores',
data: {'clubId': testClubId},
)).thenAnswer((_) async => <Map<String, dynamic>>[]);
// when
await scoreService.fetchClubScores();
// then
expect(scoreService.scores, isEmpty);
expect(scoreService.isLoading, isFalse);
expect(scoreService.lastLoadedAt, isNotNull);
expect(scoreService.lastError, isNull);
});
test('addScore 실패 시 예외 전파, isLoading=false, scores 불변', () async {
// given
final payload = {
'memberId': testMemberId,
'eventId': testEventId,
'clubId': testClubId,
'frames': [1],
'totalScore': 1,
};
when(mockApiService.post(ApiConfig.scores, data: payload))
.thenThrow(Exception('network'));
final beforeLen = scoreService.scores.length;
// when/then
expect(() async => await scoreService.addScore(payload), throwsA(isA<Exception>()));
await EventBusTestUtils.waitForIdle();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(scoreService.isLoading, isFalse);
expect(scoreService.scores.length, beforeLen);
expect(scoreService.lastError, isNotNull);
});
test('updateScore 실패 시 예외 전파, isLoading=false, scores 불변', () async {
// given: 기존 점수 1개 로드
when(mockApiService.post(
'${ApiConfig.clubs}/scores',
data: {'clubId': testClubId},
)).thenAnswer((_) async => [testScore]);
await scoreService.fetchClubScores();
final before = List.from(scoreService.scores);
when(mockApiService.put('${ApiConfig.scores}/$testScoreId', data: anyNamed('data')))
.thenThrow(Exception('update failed'));
// when/then
expect(() async => await scoreService.updateScore(testScoreId, {'totalScore': 77}), throwsA(isA<Exception>()));
await EventBusTestUtils.waitForIdle();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(scoreService.isLoading, isFalse);
expect(scoreService.scores.map((e) => e.totalScore), before.map((e) => e.totalScore));
expect(scoreService.lastError, isNotNull);
});
test('deleteScore 실패 시 예외 전파, isLoading=false, scores 불변', () async {
// given: 기존 점수 1개 로드
when(mockApiService.post(
'${ApiConfig.clubs}/scores',
data: {'clubId': testClubId},
)).thenAnswer((_) async => [testScore]);
await scoreService.fetchClubScores();
final beforeLen = scoreService.scores.length;
when(mockApiService.delete('${ApiConfig.scores}/$testScoreId'))
.thenThrow(Exception('delete failed'));
// when/then
expect(() async => await scoreService.deleteScore(testScoreId), throwsA(isA<Exception>()));
await EventBusTestUtils.waitForIdle();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(scoreService.isLoading, isFalse);
expect(scoreService.scores.length, beforeLen);
});
test('fetchMemberScores: token 또는 clubId 없으면 예외', () async {
// given: 새 서비스(초기화/클럽 미설정)
final svc = ScoreService.forTest(mockApiService);
// when/then
expect(() async => await svc.fetchMemberScores(testMemberId), throwsA(isA<Exception>()));
});
test('fetchEventScores: token 또는 clubId 없으면 예외', () async {
// given: 새 서비스(초기화/클럽 미설정)
final svc = ScoreService.forTest(mockApiService);
// when/then
expect(() async => await svc.fetchEventScores(testEventId), throwsA(isA<Exception>()));
});
test('fetchMemberStatistics 메서드가 회원 통계를 가져와야 함', () async {
// given
when(mockApiService.post(
@@ -317,6 +431,8 @@ void main() {
// when
final result = await scoreService.fetchClubStatistics();
await EventBusTestUtils.waitForIdle();
await Future<void>.delayed(const Duration(milliseconds: 50));
// then
verify(mockApiService.post(