From 45e4cfad9e576ce8a63fa2846574b705bd655437 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 16 Jul 2024 14:12:50 +0700 Subject: [PATCH] TF-2928 Write unit test for `validateUrgentException` and `onError` in BaseController --- lib/features/base/base_controller.dart | 8 +- .../exceptions/authentication_exception.dart | 9 - lib/main/exceptions/remote_exception.dart | 26 +-- test/features/base/base_controller_test.dart | 199 ++++++++++++++++++ 4 files changed, 207 insertions(+), 35 deletions(-) create mode 100644 test/features/base/base_controller_test.dart diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 86584d5748..f24dd2b487 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -111,7 +111,7 @@ abstract class BaseController extends GetxController html.window.onUnload.listen(handleBrowserReloadAction); } - Future handleBrowserBeforeReloadAction(html.Event event) async {} + Future handleBrowserBeforeReloadAction(html.Event event) async {} Future handleBrowserReloadAction(html.Event event) async {} @@ -141,7 +141,7 @@ abstract class BaseController extends GetxController viewState.value.fold( (failure) { if (failure is FeatureFailure) { - final isUrgentException = _validateUrgentException(failure.exception); + final isUrgentException = validateUrgentException(failure.exception); if (isUrgentException) { handleUrgentException(failure: failure, exception: failure.exception); } else { @@ -156,7 +156,7 @@ abstract class BaseController extends GetxController void onError(dynamic error, StackTrace stackTrace) { logError('$runtimeType::onError():Error: $error | StackTrace: $stackTrace'); - final isUrgentException = _validateUrgentException(error); + final isUrgentException = validateUrgentException(error); if (isUrgentException) { handleUrgentException(exception: error); } else { @@ -166,7 +166,7 @@ abstract class BaseController extends GetxController void onDone() {} - bool _validateUrgentException(dynamic exception) { + bool validateUrgentException(dynamic exception) { return exception is NoNetworkError || exception is BadCredentialsException || exception is ConnectionError; diff --git a/lib/features/login/domain/exceptions/authentication_exception.dart b/lib/features/login/domain/exceptions/authentication_exception.dart index a03da51e18..2fd97c29e8 100644 --- a/lib/features/login/domain/exceptions/authentication_exception.dart +++ b/lib/features/login/domain/exceptions/authentication_exception.dart @@ -11,16 +11,10 @@ abstract class AuthenticationException extends RemoteException { class BadCredentials extends AuthenticationException { BadCredentials() : super(AuthenticationException.wrongCredential); - - @override - List get props => [message]; } class BadGateway extends AuthenticationException { BadGateway() : super(AuthenticationException.badGateway); - - @override - List get props => [message]; } class NotFoundAuthenticatedAccountException implements Exception {} @@ -29,9 +23,6 @@ class NotFoundStoredTokenException implements Exception {} class InvalidBaseUrl extends AuthenticationException { InvalidBaseUrl() : super(AuthenticationException.invalidBaseUrl); - - @override - List get props => [message]; } class NotFoundAccessTokenException implements Exception {} diff --git a/lib/main/exceptions/remote_exception.dart b/lib/main/exceptions/remote_exception.dart index 235be7a938..abd77dd09e 100644 --- a/lib/main/exceptions/remote_exception.dart +++ b/lib/main/exceptions/remote_exception.dart @@ -15,48 +15,33 @@ abstract class RemoteException with EquatableMixin implements Exception { final int? code; const RemoteException({this.code, this.message}); + + @override + List get props => [message, code]; } class BadCredentialsException extends RemoteException { const BadCredentialsException() : super(message: RemoteException.badCredentials); - - @override - List get props => []; } class UnknownError extends RemoteException { const UnknownError({int? code, Object? message}) : super(code: code, message: message); - - @override - List get props => [code, message]; } class ConnectionError extends RemoteException { const ConnectionError({String? message}) : super(message: message ?? RemoteException.connectionError); - - @override - List get props => [code, message]; } class ConnectionTimeout extends RemoteException { const ConnectionTimeout({String? message}) : super(message: message ?? RemoteException.connectionTimeout); - - @override - List get props => [code, message]; } class SocketError extends RemoteException { const SocketError() : super(message: RemoteException.socketException); - - @override - List get props => [code, message]; } class InternalServerError extends RemoteException { const InternalServerError() : super(message: RemoteException.internalServerError); - - @override - List get props => [code, message]; } class MethodLevelErrors extends RemoteException { @@ -68,7 +53,7 @@ class MethodLevelErrors extends RemoteException { ) : super(message: message); @override - List get props => [type, code, message]; + List get props => [type, ...super.props]; } class CannotCalculateChangesMethodResponseException extends MethodLevelErrors { @@ -77,7 +62,4 @@ class CannotCalculateChangesMethodResponseException extends MethodLevelErrors { class NoNetworkError extends RemoteException { const NoNetworkError() : super(message: RemoteException.noNetworkError); - - @override - List get props => [code, message]; } \ No newline at end of file diff --git a/test/features/base/base_controller_test.dart b/test/features/base/base_controller_test.dart new file mode 100644 index 0000000000..916a347c0a --- /dev/null +++ b/test/features/base/base_controller_test.dart @@ -0,0 +1,199 @@ +import 'package:core/data/network/config/dynamic_url_interceptors.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/utils/app_toast.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/utils/application_manager.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:mockito/annotations.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/caching/caching_manager.dart'; +import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; +import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; +import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; +import 'package:uuid/uuid.dart'; + +import 'base_controller_test.mocks.dart'; + +class MockBaseController extends BaseController { + + bool isUrgentExceptionEnable = false; + bool isErrorViewStateEnable = false; + + void resetState() { + isUrgentExceptionEnable = false; + isErrorViewStateEnable = false; + } + + @override + void handleErrorViewState(Object error, StackTrace stackTrace) { + super.handleErrorViewState(error, stackTrace); + isErrorViewStateEnable = true; + } + + @override + void handleUrgentException({Failure? failure, Exception? exception}) { + super.handleUrgentException(failure: failure, exception: exception); + isUrgentExceptionEnable = true; + } +} + +class SomeOtherException extends RemoteException {} + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MockBaseController mockBaseController; + late MockCachingManager mockCachingManager; + late MockLanguageCacheManager mockLanguageCacheManager; + late MockAuthorizationInterceptors mockAuthorizationInterceptors; + late MockDynamicUrlInterceptors mockDynamicUrlInterceptors; + late MockDeleteCredentialInteractor mockDeleteCredentialInteractor; + late MockLogoutOidcInteractor mockLogoutOidcInteractor; + late MockDeleteAuthorityOidcInteractor mockDeleteAuthorityOidcInteractor; + late MockAppToast mockAppToast; + late MockImagePaths mockImagePaths; + late MockResponsiveUtils mockResponsiveUtils; + late MockUuid mockUuid; + late MockApplicationManager mockApplicationManager; + + setUpAll(() { + mockCachingManager = MockCachingManager(); + mockLanguageCacheManager = MockLanguageCacheManager(); + mockAuthorizationInterceptors = MockAuthorizationInterceptors(); + mockDynamicUrlInterceptors = MockDynamicUrlInterceptors(); + mockDeleteCredentialInteractor = MockDeleteCredentialInteractor(); + mockLogoutOidcInteractor = MockLogoutOidcInteractor(); + mockDeleteAuthorityOidcInteractor = MockDeleteAuthorityOidcInteractor(); + mockAppToast = MockAppToast(); + mockImagePaths = MockImagePaths(); + mockResponsiveUtils = MockResponsiveUtils(); + mockUuid = MockUuid(); + mockApplicationManager = MockApplicationManager(); + + Get.put(mockCachingManager); + Get.put(mockLanguageCacheManager); + Get.put(mockAuthorizationInterceptors); + Get.put( + mockAuthorizationInterceptors, + tag: BindingTag.isolateTag, + ); + Get.put(mockDynamicUrlInterceptors); + Get.put(mockDeleteCredentialInteractor); + Get.put(mockLogoutOidcInteractor); + Get.put(mockDeleteAuthorityOidcInteractor); + Get.put(mockAppToast); + Get.put(mockImagePaths); + Get.put(mockResponsiveUtils); + Get.put(mockUuid); + Get.put(mockApplicationManager); + Get.testMode = true; + + mockBaseController = MockBaseController(); + }); + + group('BaseController::validateUrgentException', () { + test('should return true when exception is NoNetworkError', () { + expect(mockBaseController.validateUrgentException(const NoNetworkError()), isTrue); + }); + + test('should return true when exception is BadCredentialsException', () { + expect(mockBaseController.validateUrgentException(const BadCredentialsException()), isTrue); + }); + + test('should return true when exception is ConnectionError', () { + expect(mockBaseController.validateUrgentException(const ConnectionError()), isTrue); + }); + + test('should return false when exception is SomeOtherException', () { + expect(mockBaseController.validateUrgentException(SomeOtherException()), isFalse); + }); + + test('should return false when exception is null', () { + expect(mockBaseController.validateUrgentException(null), isFalse); + }); + + test('should return false when exceptions are other types', () { + expect(mockBaseController.validateUrgentException('StringException'), isFalse); + expect(mockBaseController.validateUrgentException(123), isFalse); + expect(mockBaseController.validateUrgentException(Object()), isFalse); + }); + }); + + group('BaseController::onError', () { + test('handleUrgentException should called when error is NoNetworkError', () { + // arrange + const error = NoNetworkError(); + final stackTrace = StackTrace.current; + + // act + mockBaseController.resetState(); + mockBaseController.onError(error, stackTrace); + + // assert + expect(mockBaseController.isUrgentExceptionEnable, true); + expect(mockBaseController.isErrorViewStateEnable, false); + }); + + test('handleUrgentException should called when error is BadCredentialsException', () { + // arrange + const error = BadCredentialsException(); + final stackTrace = StackTrace.current; + + // act + mockBaseController.resetState(); + mockBaseController.onError(error, stackTrace); + + // assert + expect(mockBaseController.isUrgentExceptionEnable, true); + expect(mockBaseController.isErrorViewStateEnable, false); + }); + + test('handleUrgentException should called when error is ConnectionError', () { + // arrange + const error = ConnectionError(); + final stackTrace = StackTrace.current; + + // act + mockBaseController.resetState(); + mockBaseController.onError(error, stackTrace); + + // assert + expect(mockBaseController.isUrgentExceptionEnable, true); + expect(mockBaseController.isErrorViewStateEnable, false); + }); + + test('handleErrorViewState should called when error is SomeOtherException', () { + // arrange + final error = SomeOtherException(); + final stackTrace = StackTrace.current; + + // act + mockBaseController.resetState(); + mockBaseController.onError(error, stackTrace); + + // assert + expect(mockBaseController.isErrorViewStateEnable, true); + expect(mockBaseController.isUrgentExceptionEnable, false); + }); + }); +}