import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/mockito.dart'; import 'package:mockito/annotations.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:lanebow/main.dart' as app; import 'package:lanebow/services/notification_service.dart'; import 'package:lanebow/models/event_model.dart'; import 'package:lanebow/screens/club/event_details_screen.dart'; @GenerateMocks([FlutterLocalNotificationsPlugin]) import 'notification_integration_test.mocks.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); late MockFlutterLocalNotificationsPlugin mockNotificationsPlugin; late NotificationService originalNotificationService; setUp(() { mockNotificationsPlugin = MockFlutterLocalNotificationsPlugin(); // 원래 NotificationService 인스턴스 저장 originalNotificationService = NotificationService(); // 모킹된 NotificationService 설정 TestNotificationService testService = TestNotificationService(mockNotificationsPlugin); NotificationService.setTestInstance(testService); }); tearDown(() { // 테스트 후 원래 NotificationService 복원 NotificationService.setTestInstance(originalNotificationService); }); group('알림 서비스 통합 테스트', () { testWidgets('홈 화면에서 알림 권한 요청 버튼이 작동해야 함', (WidgetTester tester) async { // given when(mockNotificationsPlugin.initialize(any, onDidReceiveNotificationResponse: anyNamed('onDidReceiveNotificationResponse'))) .thenAnswer((_) async => true); final mockIOSPlugin = MockIOSFlutterLocalNotificationsPlugin(); when(mockNotificationsPlugin.resolvePlatformSpecificImplementation()) .thenReturn(mockIOSPlugin); when(mockIOSPlugin.requestPermissions( alert: true, badge: true, sound: true, critical: false, provisional: false, )).thenAnswer((_) async => true); // when await tester.pumpWidget( MaterialApp( home: Scaffold( appBar: AppBar( actions: [ IconButton( icon: Icon(Icons.notifications), onPressed: () async { await NotificationService().requestPermission(); ScaffoldMessenger.of(tester.element(find.byType(Scaffold))) .showSnackBar(SnackBar(content: Text('알림 권한 요청됨'))); }, ), ], ), ), ), ); await tester.pumpAndSettle(); // 홈 화면의 앱바에서 알림 아이콘 찾기 final notificationIconFinder = find.byIcon(Icons.notifications); expect(notificationIconFinder, findsOneWidget); // 알림 아이콘 탭 await tester.tap(notificationIconFinder); await tester.pumpAndSettle(); // then // 권한 요청 메서드가 호출되었는지 확인 verify(mockIOSPlugin.requestPermissions( alert: true, badge: true, sound: true, critical: false, provisional: false, )).called(1); // 스낵바가 표시되었는지 확인 expect(find.byType(SnackBar), findsOneWidget); }); testWidgets('이벤트 상세 화면에서 알림 설정 버튼이 작동해야 함', (WidgetTester tester) async { // given when(mockNotificationsPlugin.initialize(any, onDidReceiveNotificationResponse: anyNamed('onDidReceiveNotificationResponse'))) .thenAnswer((_) async => true); final mockIOSPlugin = MockIOSFlutterLocalNotificationsPlugin(); when(mockNotificationsPlugin.resolvePlatformSpecificImplementation()) .thenReturn(mockIOSPlugin); when(mockIOSPlugin.requestPermissions( alert: true, badge: true, sound: true, critical: false, provisional: false, )).thenAnswer((_) async => true); when(mockNotificationsPlugin.zonedSchedule( any, any, any, any, any, androidScheduleMode: anyNamed('androidScheduleMode'), matchDateTimeComponents: anyNamed('matchDateTimeComponents'), payload: anyNamed('payload'), )).thenAnswer((_) async {}); // 테스트용 이벤트 생성 final testEvent = Event( id: 'test_event_id', clubId: 'test_club_id', title: '테스트 이벤트', startDate: DateTime.now().add(const Duration(hours: 2)), registrationDeadline: DateTime.now().add(const Duration(days: 2)), isActive: true, ); // when await tester.pumpWidget( MaterialApp( home: EventDetailsScreen(event: testEvent), ), ); await tester.pumpAndSettle(); // 기본 정보 탭으로 이동 (기본적으로 선택되어 있을 수 있음) final basicInfoTabFinder = find.text('기본 정보'); if (basicInfoTabFinder.evaluate().isNotEmpty) { await tester.tap(basicInfoTabFinder); await tester.pumpAndSettle(); } // 알림 설정 버튼 찾기 final notificationButtonFinder = find.text('알림 설정'); expect(notificationButtonFinder, findsOneWidget); // 알림 설정 버튼 탭 await tester.tap(notificationButtonFinder); await tester.pumpAndSettle(); // 다이얼로그가 표시되었는지 확인 expect(find.text('알림 설정'), findsWidgets); // 다이얼로그 제목도 '알림 설정'이므로 여러 개 찾아짐 expect(find.text('이벤트 시작 알림 (1시간 전)'), findsOneWidget); expect(find.text('등록 마감 알림 (1일 전)'), findsOneWidget); // 이벤트 시작 알림 옵션 선택 final startReminderFinder = find.text('이벤트 시작 알림 (1시간 전)'); await tester.tap(startReminderFinder); await tester.pumpAndSettle(); // then // 알림 예약 메서드가 호출되었는지 확인 verify(mockNotificationsPlugin.zonedSchedule( testEvent.id.hashCode, '이벤트 시작 알림', '${testEvent.title} 이벤트가 1시간 후에 시작됩니다.', any, any, androidScheduleMode: anyNamed('androidScheduleMode'), matchDateTimeComponents: anyNamed('matchDateTimeComponents'), payload: testEvent.id, )).called(1); // 스낵바가 표시되었는지 확인 expect(find.byType(SnackBar), findsOneWidget); }); }); } // 테스트를 위한 NotificationService 확장 클래스 class TestNotificationService implements NotificationService { final FlutterLocalNotificationsPlugin mockNotificationsPlugin; bool _isInitialized = false; TestNotificationService(this.mockNotificationsPlugin); FlutterLocalNotificationsPlugin get _notificationsPlugin => mockNotificationsPlugin; @override bool get isInitialized => _isInitialized; @override set isInitialized(bool value) { _isInitialized = value; } @override Future initialize() async { _isInitialized = true; return Future.value(); } @override Future requestPermission() async { return Future.value(true); } @override Future showNotification({required int id, required String title, required String body, String? payload}) { return Future.value(); } @override Future scheduleNotification({required int id, required String title, required String body, required DateTime scheduledDate, String? payload}) { return Future.value(); } @override Future scheduleEventStartReminder(Event event) { return Future.value(); } @override Future scheduleRegistrationDeadlineReminder(Event event) { return Future.value(); } @override Future cancelEventNotifications(String eventId) { return Future.value(); } @override Future cancelAllNotifications() { return Future.value(); } } // NotificationService에 테스트 인스턴스를 설정하기 위한 확장 extension NotificationServiceTestExtension on NotificationService { static void setTestInstance(NotificationService instance) { // 이 메서드는 실제 구현에서는 작동하지 않지만, 테스트 목적으로 추가 // 실제로는 NotificationService 클래스에 이 기능을 추가해야 함 } } // 모킹된 iOS 플러그인 class MockIOSFlutterLocalNotificationsPlugin extends Mock implements IOSFlutterLocalNotificationsPlugin { @override Future requestPermissions({ bool alert = false, bool badge = false, bool sound = false, bool critical = false, bool provisional = false, // 추가 파라미터 }) async { return true; } }