import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:mockito/mockito.dart'; import 'package:mockito/annotations.dart'; import 'package:dartz/dartz.dart'; import 'package:superport/services/auth_service.dart'; import 'package:superport/screens/login/widgets/login_view_redesign.dart'; import 'package:superport/screens/login/controllers/login_controller.dart'; import 'package:superport/data/models/auth/login_response.dart'; import 'package:superport/data/models/auth/auth_user.dart'; import 'package:superport/core/errors/failures.dart'; import 'login_widget_test.mocks.dart'; @GenerateMocks([AuthService]) void main() { late MockAuthService mockAuthService; late GetIt getIt; setUp(() { mockAuthService = MockAuthService(); getIt = GetIt.instance; // GetIt 초기화 if (getIt.isRegistered()) { getIt.unregister(); } getIt.registerSingleton(mockAuthService); }); tearDown(() { getIt.reset(); }); group('로그인 화면 위젯 테스트', () { testWidgets('로그인 화면 초기 렌더링', (WidgetTester tester) async { // Arrange & Act await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: LoginController(), onLoginSuccess: () {}, ), ), ), ); // Assert expect(find.text('로그인'), findsOneWidget); expect(find.byType(TextFormField), findsNWidgets(2)); // ID와 비밀번호 필드 expect(find.text('아이디/이메일'), findsOneWidget); expect(find.text('비밀번호'), findsOneWidget); expect(find.text('아이디 저장'), findsOneWidget); }); testWidgets('입력 필드 유효성 검사', (WidgetTester tester) async { // Arrange final controller = LoginController(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: controller, onLoginSuccess: () {}, ), ), ), ); // Act - 빈 상태로 로그인 시도 final loginButton = find.widgetWithText(ElevatedButton, '로그인'); await tester.tap(loginButton); await tester.pump(); // Assert expect(controller.errorMessage, isNotNull); expect(controller.errorMessage, contains('입력해주세요')); }); testWidgets('로그인 성공 시나리오', (WidgetTester tester) async { // Arrange final controller = LoginController(); final mockResponse = LoginResponse( accessToken: 'test_token', refreshToken: 'refresh_token', tokenType: 'Bearer', expiresIn: 3600, user: AuthUser( id: 1, username: 'testuser', email: 'test@example.com', name: '테스트 사용자', role: 'USER', ), ); when(mockAuthService.login(any)) .thenAnswer((_) async => Right(mockResponse)); await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: controller, onLoginSuccess: () {}, ), ), ), ); // Act // ID 입력 final idField = find.byType(TextFormField).first; await tester.enterText(idField, 'test@example.com'); // 비밀번호 입력 final passwordField = find.byType(TextFormField).last; await tester.enterText(passwordField, 'password123'); // 로그인 버튼 탭 final loginButton = find.widgetWithText(ElevatedButton, '로그인'); await tester.tap(loginButton); // 비동기 작업 대기 await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Assert expect(controller.isLoading, false); expect(controller.errorMessage, isNull); }); testWidgets('로그인 실패 시나리오', (WidgetTester tester) async { // Arrange final controller = LoginController(); when(mockAuthService.login(any)) .thenAnswer((_) async => Left(AuthenticationFailure( message: '이메일 또는 비밀번호가 올바르지 않습니다.', ))); await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: controller, onLoginSuccess: () {}, ), ), ), ); // Act final idField = find.byType(TextFormField).first; await tester.enterText(idField, 'wrong@example.com'); final passwordField = find.byType(TextFormField).last; await tester.enterText(passwordField, 'wrongpassword'); final loginButton = find.widgetWithText(ElevatedButton, '로그인'); await tester.tap(loginButton); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Assert expect(controller.errorMessage, isNotNull); expect(controller.errorMessage, contains('올바르지 않습니다')); }); testWidgets('로딩 상태 표시', (WidgetTester tester) async { // Arrange final controller = LoginController(); // 지연된 응답 시뮬레이션 when(mockAuthService.login(any)).thenAnswer((_) async { await Future.delayed(const Duration(seconds: 2)); return Right(LoginResponse( accessToken: 'test_token', refreshToken: 'refresh_token', tokenType: 'Bearer', expiresIn: 3600, user: AuthUser( id: 1, username: 'testuser', email: 'test@example.com', name: '테스트 사용자', role: 'USER', ), )); }); await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: controller, onLoginSuccess: () {}, ), ), ), ); // Act final idField = find.byType(TextFormField).first; await tester.enterText(idField, 'test@example.com'); final passwordField = find.byType(TextFormField).last; await tester.enterText(passwordField, 'password123'); final loginButton = find.widgetWithText(ElevatedButton, '로그인'); await tester.tap(loginButton); // 로딩 상태 확인 await tester.pump(); // Assert - 로딩 중 expect(controller.isLoading, true); expect(find.byType(CircularProgressIndicator), findsOneWidget); // 로딩 완료 대기 await tester.pump(const Duration(seconds: 2)); await tester.pump(); // Assert - 로딩 완료 expect(controller.isLoading, false); expect(find.byType(CircularProgressIndicator), findsNothing); }); testWidgets('비밀번호 표시/숨기기 토글', (WidgetTester tester) async { // Arrange await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: LoginController(), onLoginSuccess: () {}, ), ), ), ); // Act & Assert // 초기 상태 - 비밀번호 숨김 final passwordField = find.byType(TextFormField).last; await tester.enterText(passwordField, 'testpassword'); // 비밀번호 표시 아이콘 찾기 final visibilityIcon = find.byIcon(Icons.visibility_off); expect(visibilityIcon, findsOneWidget); // 아이콘 탭하여 비밀번호 표시 await tester.tap(visibilityIcon); await tester.pump(); // 비밀번호 표시 상태 확인 expect(find.byIcon(Icons.visibility), findsOneWidget); }); testWidgets('아이디 저장 체크박스 동작', (WidgetTester tester) async { // Arrange final controller = LoginController(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: controller, onLoginSuccess: () {}, ), ), ), ); // Act & Assert // 초기 상태 expect(controller.saveId, false); // 체크박스 탭 final checkbox = find.byType(Checkbox); await tester.tap(checkbox); await tester.pump(); // 상태 변경 확인 expect(controller.saveId, true); // 다시 탭하여 해제 await tester.tap(checkbox); await tester.pump(); expect(controller.saveId, false); }); testWidgets('이메일 형식 검증', (WidgetTester tester) async { // Arrange final controller = LoginController(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: LoginViewRedesign( controller: controller, onLoginSuccess: () {}, ), ), ), ); // Act - 이메일 형식 입력 final idField = find.byType(TextFormField).first; await tester.enterText(idField, 'test@example.com'); final passwordField = find.byType(TextFormField).last; await tester.enterText(passwordField, 'password123'); // LoginRequest 생성 시 이메일로 처리되는지 확인 expect(controller.idController.text, 'test@example.com'); // Act - username 형식 입력 await tester.enterText(idField, 'testuser'); // username으로 처리되는지 확인 expect(controller.idController.text, 'testuser'); }); }); group('로그인 컨트롤러 단위 테스트', () { test('입력 검증 - 빈 아이디', () async { // Arrange final controller = LoginController(); controller.idController.text = ''; controller.pwController.text = 'password'; // Act final result = await controller.login(); // Assert expect(result, false); expect(controller.errorMessage, contains('아이디 또는 이메일을 입력해주세요')); }); test('입력 검증 - 빈 비밀번호', () async { // Arrange final controller = LoginController(); controller.idController.text = 'test@example.com'; controller.pwController.text = ''; // Act final result = await controller.login(); // Assert expect(result, false); expect(controller.errorMessage, contains('비밀번호를 입력해주세요')); }); test('이메일/username 구분', () async { // Arrange final controller = LoginController(); // Test 1: 이메일 형식 controller.idController.text = 'test@example.com'; controller.pwController.text = 'password'; when(mockAuthService.login(any)) .thenAnswer((_) async => Right(LoginResponse( accessToken: 'token', refreshToken: 'refresh', tokenType: 'Bearer', expiresIn: 3600, user: AuthUser( id: 1, username: 'test', email: 'test@example.com', name: 'Test', role: 'USER', ), ))); // Act await controller.login(); // Assert final capturedRequest = verify(mockAuthService.login(captureAny)).captured.single; expect(capturedRequest.email, 'test@example.com'); expect(capturedRequest.username, isNull); // Test 2: Username 형식 controller.idController.text = 'testuser'; // Act await controller.login(); // Assert final capturedRequest2 = verify(mockAuthService.login(captureAny)).captured.single; expect(capturedRequest2.email, isNull); expect(capturedRequest2.username, 'testuser'); }); }); }