From 56d131e7936dec4bd41a94a0e5aab485f34364ad Mon Sep 17 00:00:00 2001 From: Efrain Date: Mon, 28 Feb 2022 20:26:49 -0500 Subject: [PATCH] [Tests] Added a few donations test --- .../donations/donations_bloc_test.dart | 241 ++++++++++++++++++ test/application/main/main_bloc_test.dart | 77 ++---- .../settings/settings_bloc_test.dart | 17 +- 3 files changed, 274 insertions(+), 61 deletions(-) create mode 100644 test/application/donations/donations_bloc_test.dart diff --git a/test/application/donations/donations_bloc_test.dart b/test/application/donations/donations_bloc_test.dart new file mode 100644 index 000000000..008a55cc0 --- /dev/null +++ b/test/application/donations/donations_bloc_test.dart @@ -0,0 +1,241 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:shiori/application/bloc.dart'; +import 'package:shiori/domain/models/models.dart'; + +import '../../mocks.mocks.dart'; + +void main() { + const _validUserId = '12345_suffix'; + const _packages = [ + PackageItemModel(identifier: '123', offeringIdentifier: 'xyz', productIdentifier: 'xxx', priceString: '2\$'), + PackageItemModel(identifier: '456', offeringIdentifier: 'xyz', productIdentifier: 'yyy', priceString: '5\$'), + PackageItemModel(identifier: '789', offeringIdentifier: 'xyz', productIdentifier: 'zzz', priceString: '10\$'), + ]; + + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + test('Initial state', () => expect(DonationsBloc(MockPurchaseService(), MockNetworkService()).state, const DonationsState.loading())); + + group('init', () { + blocTest( + 'network is not available', + build: () { + final networkService = MockNetworkService(); + final purchaseService = MockPurchaseService(); + when(networkService.isInternetAvailable()).thenAnswer((_) => Future.value(false)); + return DonationsBloc(purchaseService, networkService); + }, + act: (bloc) => bloc.add(const DonationsEvent.init()), + expect: () => const [ + DonationsState.loading(), + DonationsState.initial( + packages: [], + isInitialized: false, + noInternetConnection: true, + canMakePurchases: false, + ) + ], + ); + + blocTest( + 'platform is not supported', + build: () { + final networkService = MockNetworkService(); + final purchaseService = MockPurchaseService(); + when(networkService.isInternetAvailable()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isPlatformSupported()).thenAnswer((_) => Future.value(false)); + return DonationsBloc(purchaseService, networkService); + }, + act: (bloc) => bloc.add(const DonationsEvent.init()), + expect: () => const [ + DonationsState.loading(), + DonationsState.initial( + packages: [], + isInitialized: false, + noInternetConnection: false, + canMakePurchases: false, + ) + ], + ); + + blocTest( + 'cannot make purchases', + build: () { + final networkService = MockNetworkService(); + final purchaseService = MockPurchaseService(); + when(networkService.isInternetAvailable()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isPlatformSupported()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isInitialized).thenReturn(true); + when(purchaseService.init()).thenAnswer((_) => Future.value(true)); + when(purchaseService.canMakePurchases()).thenAnswer((_) => Future.value(false)); + return DonationsBloc(purchaseService, networkService); + }, + act: (bloc) => bloc.add(const DonationsEvent.init()), + expect: () => const [ + DonationsState.loading(), + DonationsState.initial( + packages: [], + isInitialized: true, + noInternetConnection: false, + canMakePurchases: false, + ), + ], + ); + + blocTest( + 'purchases are loaded', + build: () { + final networkService = MockNetworkService(); + final purchaseService = MockPurchaseService(); + when(networkService.isInternetAvailable()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isPlatformSupported()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isInitialized).thenReturn(true); + when(purchaseService.init()).thenAnswer((_) => Future.value(true)); + when(purchaseService.canMakePurchases()).thenAnswer((_) => Future.value(true)); + when(purchaseService.getInAppPurchases()).thenAnswer((_) => Future.value(_packages)); + return DonationsBloc(purchaseService, networkService); + }, + act: (bloc) => bloc.add(const DonationsEvent.init()), + expect: () => const [ + DonationsState.loading(), + DonationsState.initial( + packages: _packages, + isInitialized: true, + noInternetConnection: false, + canMakePurchases: true, + ), + ], + ); + }); + + group('restore purchases', () { + DonationsBloc _getBloc({bool restoreSucceeds = true}) { + final networkService = MockNetworkService(); + final purchaseService = MockPurchaseService(); + when(networkService.isInternetAvailable()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isPlatformSupported()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isInitialized).thenReturn(true); + when(purchaseService.init()).thenAnswer((_) => Future.value(true)); + when(purchaseService.canMakePurchases()).thenAnswer((_) => Future.value(true)); + when(purchaseService.getInAppPurchases()).thenAnswer((_) => Future.value(_packages)); + when(purchaseService.restorePurchases(_validUserId)).thenAnswer((_) => Future.value(restoreSucceeds)); + return DonationsBloc(purchaseService, networkService); + } + + blocTest( + 'invalid userid', + build: () => _getBloc(), + act: (bloc) => bloc..add(const DonationsEvent.restorePurchases(userId: 'xxxx_1234')), + errors: () => [isA()], + expect: () => const [ + DonationsState.loading(), + ], + ); + + blocTest( + 'succeeds', + build: () => _getBloc(), + act: (bloc) => bloc..add(const DonationsEvent.restorePurchases(userId: _validUserId)), + expect: () => const [ + DonationsState.loading(), + DonationsState.restoreCompleted(error: false), + ], + ); + + blocTest( + 'fails', + build: () => _getBloc(restoreSucceeds: false), + act: (bloc) => bloc..add(const DonationsEvent.restorePurchases(userId: _validUserId)), + expect: () => const [ + DonationsState.loading(), + DonationsState.restoreCompleted(error: true), + DonationsState.initial(packages: _packages, isInitialized: true, noInternetConnection: false, canMakePurchases: true), + ], + ); + }); + + group('purchase', () { + DonationsBloc _getBloc({bool purchaseSucceeds = true}) { + final networkService = MockNetworkService(); + final purchaseService = MockPurchaseService(); + when(networkService.isInternetAvailable()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isPlatformSupported()).thenAnswer((_) => Future.value(true)); + when(purchaseService.isInitialized).thenReturn(true); + when(purchaseService.init()).thenAnswer((_) => Future.value(true)); + when(purchaseService.canMakePurchases()).thenAnswer((_) => Future.value(true)); + when(purchaseService.getInAppPurchases()).thenAnswer((_) => Future.value(_packages)); + when(purchaseService.purchase(_validUserId, _packages.first.identifier, _packages.first.offeringIdentifier)) + .thenAnswer((_) => Future.value(purchaseSucceeds)); + return DonationsBloc(purchaseService, networkService); + } + + blocTest( + 'invalid user id', + build: () => _getBloc(), + act: (bloc) => bloc..add(const DonationsEvent.purchase(userId: '123_xyz', identifier: '', offeringIdentifier: '')), + errors: () => [isA()], + expect: () => const [ + DonationsState.loading(), + ], + ); + + blocTest( + 'invalid identifier', + build: () => _getBloc(), + act: (bloc) => bloc..add(const DonationsEvent.purchase(userId: _validUserId, identifier: '', offeringIdentifier: '')), + errors: () => [isA()], + expect: () => const [ + DonationsState.loading(), + ], + ); + + blocTest( + 'invalid offering identifier', + build: () => _getBloc(), + act: (bloc) => bloc..add(DonationsEvent.purchase(userId: _validUserId, identifier: _packages.first.identifier, offeringIdentifier: '')), + errors: () => [isA()], + expect: () => const [ + DonationsState.loading(), + ], + ); + + blocTest( + 'succeed', + build: () => _getBloc(), + act: (bloc) => bloc + ..add( + DonationsEvent.purchase( + userId: _validUserId, + identifier: _packages.first.identifier, + offeringIdentifier: _packages.first.offeringIdentifier, + ), + ), + expect: () => const [ + DonationsState.loading(), + DonationsState.purchaseCompleted(error: false), + ], + ); + + blocTest( + 'succeeds', + build: () => _getBloc(purchaseSucceeds: false), + act: (bloc) => bloc + ..add( + DonationsEvent.purchase( + userId: _validUserId, + identifier: _packages.first.identifier, + offeringIdentifier: _packages.first.offeringIdentifier, + ), + ), + expect: () => const [ + DonationsState.loading(), + DonationsState.purchaseCompleted(error: true), + DonationsState.initial(packages: _packages, isInitialized: true, noInternetConnection: false, canMakePurchases: true), + ], + ); + }); +} diff --git a/test/application/main/main_bloc_test.dart b/test/application/main/main_bloc_test.dart index 1cfba365b..a219c8c86 100644 --- a/test/application/main/main_bloc_test.dart +++ b/test/application/main/main_bloc_test.dart @@ -9,6 +9,7 @@ import 'package:shiori/domain/services/device_info_service.dart'; import 'package:shiori/domain/services/genshin_service.dart'; import 'package:shiori/domain/services/locale_service.dart'; import 'package:shiori/domain/services/logging_service.dart'; +import 'package:shiori/domain/services/purchase_service.dart'; import 'package:shiori/domain/services/settings_service.dart'; import 'package:shiori/domain/services/telemetry_service.dart'; import 'package:shiori/infrastructure/infrastructure.dart'; @@ -70,6 +71,7 @@ void main() { late final LocaleService _localeService; late final TelemetryService _telemetryService; late final DeviceInfoService _deviceInfoService; + late final PurchaseService _purchaseService; late final CharactersBloc _charactersBloc; late final WeaponsBloc _weaponsBloc; @@ -94,6 +96,8 @@ void main() { _deviceInfoService = MockDeviceInfoService(); _localeService = LocaleServiceImpl(_settingsService); _genshinService = GenshinServiceImpl(_localeService); + _purchaseService = MockPurchaseService(); + when(_purchaseService.getUnlockedFeatures()).thenAnswer((_) => Future.value(AppUnlockedFeature.values)); _charactersBloc = MockCharactersBloc(); _weaponsBloc = MockWeaponsBloc(); @@ -112,37 +116,29 @@ void main() { when(_deviceInfoService.versionChanged).thenReturn(false); }); - test('Initial state', () { - final bloc = MainBloc( - _logger, - _genshinService, - _settingsService, - _localeService, - _telemetryService, - _deviceInfoService, - _charactersBloc, - _weaponsBloc, - _homeBloc, - _artifactsBloc, - ); - expect(bloc.state, const MainState.loading()); - }); - - group('Init', () { - blocTest( - 'emits init state', - build: () => MainBloc( + MainBloc _getBloc() => MainBloc( _logger, _genshinService, _settingsService, _localeService, _telemetryService, _deviceInfoService, + _purchaseService, _charactersBloc, _weaponsBloc, _homeBloc, _artifactsBloc, - ), + ); + + test('Initial state', () { + final bloc = _getBloc(); + expect(bloc.state, const MainState.loading()); + }); + + group('Init', () { + blocTest( + 'emits init state', + build: () => _getBloc(), act: (bloc) => bloc.add(const MainEvent.init()), expect: () => [ MainState.loaded( @@ -161,18 +157,7 @@ void main() { group('Theme changed', () { blocTest( 'updates the theme in the state', - build: () => MainBloc( - _logger, - _genshinService, - _settingsService, - _localeService, - _telemetryService, - _deviceInfoService, - _charactersBloc, - _weaponsBloc, - _homeBloc, - _artifactsBloc, - ), + build: () => _getBloc(), act: (bloc) => bloc ..add(const MainEvent.init()) ..add(const MainEvent.themeChanged(newValue: AppThemeType.light)), @@ -192,18 +177,7 @@ void main() { blocTest( 'updates the accent color in the state', - build: () => MainBloc( - _logger, - _genshinService, - _settingsService, - _localeService, - _telemetryService, - _deviceInfoService, - _charactersBloc, - _weaponsBloc, - _homeBloc, - _artifactsBloc, - ), + build: () => _getBloc(), act: (bloc) => bloc ..add(const MainEvent.init()) ..add(const MainEvent.accentColorChanged(newValue: AppAccentColorType.blueGrey)), @@ -225,18 +199,7 @@ void main() { group('Language changed', () { blocTest( 'updates the language in the state', - build: () => MainBloc( - _logger, - _genshinService, - _settingsService, - _localeService, - _telemetryService, - _deviceInfoService, - _charactersBloc, - _weaponsBloc, - _homeBloc, - _artifactsBloc, - ), + build: () => _getBloc(), setUp: () { when(_settingsService.language).thenReturn(AppLanguageType.russian); }, diff --git a/test/application/settings/settings_bloc_test.dart b/test/application/settings/settings_bloc_test.dart index 920374aa6..c25888cf8 100644 --- a/test/application/settings/settings_bloc_test.dart +++ b/test/application/settings/settings_bloc_test.dart @@ -5,6 +5,7 @@ import 'package:shiori/application/bloc.dart'; import 'package:shiori/domain/enums/enums.dart'; import 'package:shiori/domain/models/models.dart'; import 'package:shiori/domain/services/device_info_service.dart'; +import 'package:shiori/domain/services/purchase_service.dart'; import 'package:shiori/domain/services/settings_service.dart'; import '../../mocks.mocks.dart'; @@ -27,6 +28,7 @@ class FakeUrlPageBloc extends Fake implements UrlPageBloc { void main() { late final SettingsService _settingsService; late final DeviceInfoService _deviceInfoService; + late final PurchaseService _purchaseService; late final MainBloc _mainBloc; late final HomeBloc _homeBloc; @@ -62,26 +64,31 @@ void main() { when(_deviceInfoService.version).thenReturn('1.0.0'); when(_deviceInfoService.appName).thenReturn('Shiori'); + _purchaseService = MockPurchaseService(); + when(_purchaseService.getUnlockedFeatures()).thenAnswer((_) => Future.value(AppUnlockedFeature.values)); + _mainBloc = FakeMainBloc(); _homeBloc = FakeHomeBloc(); }); + SettingsBloc _getBloc() => SettingsBloc(_settingsService, _deviceInfoService, _purchaseService, _mainBloc, _homeBloc); + test( 'Initial state', - () => expect(SettingsBloc(_settingsService, _deviceInfoService, _mainBloc, _homeBloc).state, const SettingsState.loading()), + () => expect(_getBloc().state, const SettingsState.loading()), ); test( 'Double back to close returns valid value', () => expect( - SettingsBloc(_settingsService, _deviceInfoService, _mainBloc, _homeBloc).doubleBackToClose(), + _getBloc().doubleBackToClose(), _defaultSettings.doubleBackToClose, ), ); blocTest( 'Init', - build: () => SettingsBloc(_settingsService, _deviceInfoService, _mainBloc, _homeBloc), + build: () => _getBloc(), act: (bloc) => bloc.add(const SettingsEvent.init()), expect: () => [ SettingsState.loaded( @@ -95,13 +102,14 @@ void main() { doubleBackToClose: _defaultSettings.doubleBackToClose, useOfficialMap: _defaultSettings.useOfficialMap, useTwentyFourHoursFormat: _defaultSettings.useTwentyFourHoursFormat, + unlockedFeatures: AppUnlockedFeature.values, ), ], ); blocTest( 'Settings changed', - build: () => SettingsBloc(_settingsService, _deviceInfoService, _mainBloc, _homeBloc), + build: () => _getBloc(), act: (bloc) => bloc ..add(const SettingsEvent.init()) ..add(const SettingsEvent.themeChanged(newValue: AppThemeType.light)) @@ -126,6 +134,7 @@ void main() { doubleBackToClose: !_defaultSettings.doubleBackToClose, useOfficialMap: !_defaultSettings.useOfficialMap, useTwentyFourHoursFormat: !_defaultSettings.useTwentyFourHoursFormat, + unlockedFeatures: AppUnlockedFeature.values, ), ], );