diff --git a/lib/core/data/datasources/account_remote_data_source.dart b/lib/core/data/datasources/account_remote_data_source.dart index 996986ff6..615142f69 100644 --- a/lib/core/data/datasources/account_remote_data_source.dart +++ b/lib/core/data/datasources/account_remote_data_source.dart @@ -23,42 +23,35 @@ class AccountRemoteDataSource { Future> login( String email, String encodedPasscode, - ) async { - return executor.executeAndMap( - () => apiV1.apiV1AccountLoginPost( - body: LoginDto( - email: email, - password: encodedPasscode, - version: ApiUriConstants.minAppVersion, - ), - ), - (result) => AuthenticatedUser( - email: email, - token: result.token!, - ), - ); + ) { + return executor + .execute( + () => apiV1.apiV1AccountLoginPost( + body: LoginDto( + email: email, + password: encodedPasscode, + version: ApiUriConstants.minAppVersion, + ), + ), + ) + .map((result) => AuthenticatedUser(email: email, token: result.token!)); } - Future> getUser() async { - return executor.executeAndMap( - apiV2.apiV2AccountGet, - UserModel.fromResponse, - ); + Future> getUser() { + return executor.execute(apiV2.apiV2AccountGet).map(UserModel.fromResponse); } Future> requestPasscodeReset(String email) { - return executor.executeAndMap( - () => apiV1.apiV1AccountForgotpasswordPost(body: EmailDto(email: email)), - (_) => unit, + final body = EmailDto(email: email); + return executor.executeAndDiscard( + () => apiV1.apiV1AccountForgotpasswordPost(body: body), ); } - Future> emailExists(String email) async { - return executor.executeAndMap( - () => apiV2.apiV2AccountEmailExistsPost( - body: EmailExistsRequest(email: email), - ), - (result) => result.emailExists, - ); + Future> emailExists(String email) { + final body = EmailExistsRequest(email: email); + return executor + .execute(() => apiV2.apiV2AccountEmailExistsPost(body: body)) + .map((result) => result.emailExists); } } diff --git a/lib/core/network/network_request_executor.dart b/lib/core/network/network_request_executor.dart index 8b146bdaf..1395ec938 100644 --- a/lib/core/network/network_request_executor.dart +++ b/lib/core/network/network_request_executor.dart @@ -4,7 +4,10 @@ import 'package:coffeecard/utils/firebase_analytics_event_logging.dart'; import 'package:fpdart/fpdart.dart'; import 'package:logger/logger.dart'; -typedef _NetworkRequest = Future> Function(); +part 'network_request_executor_mapping.dart'; + +typedef _NetworkRequest = Future> Function(); +typedef _ExecutorResult = Future>; class NetworkRequestExecutor { final Logger logger; @@ -15,42 +18,15 @@ class NetworkRequestExecutor { required this.firebaseLogger, }); - /// Executes the network [request] and returns the result as an [Either]. - Future> execute(_NetworkRequest request) { - return _execute(request); - } - - /// Executes the network [request] and returns the result as an [Either], - /// where the Right value of type [ResponseType] is transformed to an [R] - /// using the given [transformer]. - Future> executeAndMap( - _NetworkRequest request, - R Function(ResponseType) transformer, - ) async { - final result = await execute(request); - return result.map(transformer); - } - - /// Executes the network [request] and returns the result as an [Either], - /// where the Right value is an [Iterable] of [ResponseType], where each - /// element herein is transformed to an [R] using the given [transformer]. + /// Executes a network request and returns an [Either]. /// - /// The result is returned as a [List]. - // TODO(marfavi): return Iterable instead of List? - Future>> executeAndMapAll( - _NetworkRequest> request, - R Function(ResponseType) transformer, - ) { - return executeAndMap( - request, - (items) => items.map(transformer).toList(), - ); - } - - /// Executes a network request and returns the result as an [Either]. - Future> _execute( - Future> Function() request, - ) async { + /// If the request fails, a [NetworkFailure] is returned in a [Left]. + /// If the request succeeds, the response body of type + /// [Body] is returned in a [Right]. + /// + /// If the response body type is empty or dynamic, use [executeAndDiscard] + /// instead, which always returns [Unit] in a [Right] if the request succeeds. + _ExecutorResult execute(_NetworkRequest request) async { try { final response = await request(); @@ -59,8 +35,7 @@ class NetworkRequestExecutor { _logResponse(response); return Left(ServerFailure.fromResponse(response)); } - - return Right(response.body as Result); + return Right(response.body as Body); } on Exception catch (e) { // could not connect to backend for whatever reason logger.e(e.toString()); @@ -68,11 +43,25 @@ class NetworkRequestExecutor { } } + /// Executes the network [request] and returns the result as an [Either]. + /// + /// If the request fails, a [NetworkFailure] is returned in a [Left]. + /// If the request succeeds, [Unit] is returned in a [Right] and + /// the orignial response body is discarded. + /// + /// This method is useful as it allows to discard the response body when its + /// type is dynamic, e.g. when it is expected to be empty. This avoids having + /// to provide dummy values for dynamic response bodies in Mockito tests. + _ExecutorResult executeAndDiscard(_NetworkRequest request) async { + final result = await execute(request); + return result.map((_) => unit); + } + /// Logs the response to the console and to Firebase. /// /// Does not log 401 responses to Firebase since these are expected when /// the user is not logged in. - void _logResponse(Response response) { + void _logResponse(Response response) { logger.e(response.toString()); if (response.statusCode != 401) { diff --git a/lib/core/network/network_request_executor_mapping.dart b/lib/core/network/network_request_executor_mapping.dart new file mode 100644 index 000000000..8a0f11dac --- /dev/null +++ b/lib/core/network/network_request_executor_mapping.dart @@ -0,0 +1,19 @@ +part of 'network_request_executor.dart'; + +extension ExecutorMapX on _ExecutorResult { + /// If the result of the [Future] is a [Right], maps the value to a [C]. + _ExecutorResult map(C Function(R) mapper) async { + final result = await this; + return result.map(mapper); + } +} + +extension ExecutorMapAllX on _ExecutorResult> { + /// If the result of the [Future] is a [Right], + /// maps all values to a [List] of [C]. + // TODO(marfavi): return Iterable instead of List? + _ExecutorResult> mapAll(C Function(R) mapper) async { + final result = await this; + return result.map((items) => items.map(mapper).toList()); + } +} diff --git a/lib/features/environment/data/datasources/environment_remote_data_source.dart b/lib/features/environment/data/datasources/environment_remote_data_source.dart index e37a92d0b..ebd2b3f61 100644 --- a/lib/features/environment/data/datasources/environment_remote_data_source.dart +++ b/lib/features/environment/data/datasources/environment_remote_data_source.dart @@ -13,10 +13,9 @@ class EnvironmentRemoteDataSource { final CoffeecardApiV2 apiV2; final NetworkRequestExecutor executor; - Future> getEnvironmentType() async { - return executor.executeAndMap( - apiV2.apiV2AppconfigGet, - Environment.fromAppConfig, - ); + Future> getEnvironmentType() { + return executor + .execute(apiV2.apiV2AppconfigGet) + .map(Environment.fromAppConfig); } } diff --git a/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart b/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart index 69ca25289..d077c8d17 100644 --- a/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart +++ b/lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart @@ -27,18 +27,18 @@ class LeaderboardRemoteDataSource { LeaderboardFilter category, int top, ) { - return executor.executeAndMap( - () => apiV2.apiV2LeaderboardTopGet(preset: category.label, top: top), - (entries) => entries.map(LeaderboardUserModel.fromDTO).toList(), - ); + return executor + .execute( + () => apiV2.apiV2LeaderboardTopGet(preset: category.label, top: top), + ) + .mapAll(LeaderboardUserModel.fromDTO); } Future> getLeaderboardUser( LeaderboardFilter category, ) { - return executor.executeAndMap( - () => apiV2.apiV2LeaderboardGet(preset: category.label), - LeaderboardUserModel.fromDTO, - ); + return executor + .execute(() => apiV2.apiV2LeaderboardGet(preset: category.label)) + .map(LeaderboardUserModel.fromDTO); } } diff --git a/lib/features/occupation/data/datasources/occupation_remote_data_source.dart b/lib/features/occupation/data/datasources/occupation_remote_data_source.dart index 522a14821..e78388248 100644 --- a/lib/features/occupation/data/datasources/occupation_remote_data_source.dart +++ b/lib/features/occupation/data/datasources/occupation_remote_data_source.dart @@ -14,9 +14,8 @@ class OccupationRemoteDataSource { }); Future>> getOccupations() { - return executor.executeAndMap( - api.apiV1ProgrammesGet, - (occupations) => occupations.map(OccupationModel.fromDTOV1).toList(), - ); + return executor + .execute(api.apiV1ProgrammesGet) + .mapAll(OccupationModel.fromDTOV1); } } diff --git a/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart b/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart index c16acc7eb..3a530624e 100644 --- a/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart +++ b/lib/features/opening_hours/data/datasources/opening_hours_remote_data_source.dart @@ -19,18 +19,16 @@ class OpeningHoursRemoteDataSource { /// Check if the cafe is open. Future> isOpen() { - return executor.executeAndMap( - () async => api.apiOpenShortKeyGet(shortKey: shortkey), - (result) => result.open, - ); + return executor + .execute(() => api.apiOpenShortKeyGet(shortKey: shortkey)) + .map((result) => result.open); } /// Get the opening hours of the cafe, including today's opening hours and /// the opening hours for the next 7 days. - Future> getOpeningHours() async { - return executor.executeAndMap( - () => api.apiShiftsShortKeyGet(shortKey: shortkey), - OpeningHoursModel.fromDTO, - ); + Future> getOpeningHours() { + return executor + .execute(() => api.apiShiftsShortKeyGet(shortKey: shortkey)) + .map(OpeningHoursModel.fromDTO); } } diff --git a/lib/features/product/data/datasources/product_remote_data_source.dart b/lib/features/product/data/datasources/product_remote_data_source.dart index 3ec59406e..212eb6729 100644 --- a/lib/features/product/data/datasources/product_remote_data_source.dart +++ b/lib/features/product/data/datasources/product_remote_data_source.dart @@ -14,10 +14,9 @@ class ProductRemoteDataSource { required this.executor, }); - Future>> getProducts() async { - return executor.executeAndMap( - apiV1.apiV1ProductsGet, - (products) => products.map(ProductModel.fromDTO).toList(), - ); + Future>> getProducts() { + return executor + .execute(apiV1.apiV1ProductsGet) + .mapAll(ProductModel.fromDTO); } } diff --git a/lib/features/purchase/data/datasources/purchase_remote_data_source.dart b/lib/features/purchase/data/datasources/purchase_remote_data_source.dart index 5cdde236e..a641561c3 100644 --- a/lib/features/purchase/data/datasources/purchase_remote_data_source.dart +++ b/lib/features/purchase/data/datasources/purchase_remote_data_source.dart @@ -21,25 +21,23 @@ class PurchaseRemoteDataSource { Future> initiatePurchase( int productId, PaymentType paymentType, - ) async { - return executor.executeAndMap( - () => apiV2.apiV2PurchasesPost( - body: InitiatePurchaseRequest( - productId: productId, - paymentType: paymentTypeToJson(paymentType), - ), - ), - InitiatePurchaseModel.fromDto, - ); + ) { + return executor + .execute( + () => apiV2.apiV2PurchasesPost( + body: InitiatePurchaseRequest( + productId: productId, + paymentType: paymentTypeToJson(paymentType), + ), + ), + ) + .map(InitiatePurchaseModel.fromDto); } /// Get a purchase by its purchase id - Future> getPurchase( - int purchaseId, - ) async { - return executor.executeAndMap( - () => apiV2.apiV2PurchasesIdGet(id: purchaseId), - SinglePurchaseModel.fromDto, - ); + Future> getPurchase(int purchaseId) { + return executor + .execute(() => apiV2.apiV2PurchasesIdGet(id: purchaseId)) + .map(SinglePurchaseModel.fromDto); } } diff --git a/lib/features/receipt/data/datasources/receipt_remote_data_source.dart b/lib/features/receipt/data/datasources/receipt_remote_data_source.dart index dc0fe4322..1879bea61 100644 --- a/lib/features/receipt/data/datasources/receipt_remote_data_source.dart +++ b/lib/features/receipt/data/datasources/receipt_remote_data_source.dart @@ -16,18 +16,16 @@ class ReceiptRemoteDataSource { }); /// Retrieves all of the users used receipts - Future>> getUsersUsedTicketsReceipts() async { - return executor.executeAndMapAll( - () => apiV2.apiV2TicketsGet(includeUsed: true), - SwipeReceiptModel.fromTicketResponse, - ); + Future>> getUsersUsedTicketsReceipts() { + return executor + .execute(() => apiV2.apiV2TicketsGet(includeUsed: true)) + .mapAll(SwipeReceiptModel.fromTicketResponse); } /// Retrieves all of the users purchase receipts - Future>> getUserPurchasesReceipts() async { - return executor.executeAndMapAll( - apiV2.apiV2PurchasesGet, - PurchaseReceiptModel.fromSimplePurchaseResponse, - ); + Future>> getUserPurchasesReceipts() { + return executor + .execute(apiV2.apiV2PurchasesGet) + .mapAll(PurchaseReceiptModel.fromSimplePurchaseResponse); } } diff --git a/lib/features/register/data/datasources/register_remote_data_source.dart b/lib/features/register/data/datasources/register_remote_data_source.dart index 8a017cbbf..2a6719be5 100644 --- a/lib/features/register/data/datasources/register_remote_data_source.dart +++ b/lib/features/register/data/datasources/register_remote_data_source.dart @@ -17,8 +17,8 @@ class RegisterRemoteDataSource { String email, String encodedPasscode, int occupationId, - ) async { - return executor.executeAndMap( + ) { + return executor.executeAndDiscard( () => apiV2.apiV2AccountPost( body: RegisterAccountRequest( name: name, @@ -27,7 +27,6 @@ class RegisterRemoteDataSource { programmeId: occupationId, ), ), - (_) => unit, ); } } diff --git a/lib/features/ticket/data/datasources/ticket_remote_data_source.dart b/lib/features/ticket/data/datasources/ticket_remote_data_source.dart index 64509f307..68f824921 100644 --- a/lib/features/ticket/data/datasources/ticket_remote_data_source.dart +++ b/lib/features/ticket/data/datasources/ticket_remote_data_source.dart @@ -19,35 +19,39 @@ class TicketRemoteDataSource { final CoffeecardApiV2 apiV2; final NetworkRequestExecutor executor; - Future>> - getUserTickets() async { - return executor.executeAndMap( - () => apiV2.apiV2TicketsGet(includeUsed: false), - (result) => result - .groupListsBy((t) => t.productId) - .entries - .map( - (entry) { - final MapEntry(key: id, value: tickets) = entry; - // Join ticket names if they share the same product id - final ticketName = - tickets.map((t) => t.productName).toSet().join('/'); - return TicketCountModel( - count: tickets.length, - productName: ticketName, - productId: id, - ); - }, - ) - .sortedBy((t) => t.productId) - .toList(), - ); + Future>> getUserTickets() { + // Mapper function for mapping a list of tickets (all with the same product + // id) to a TicketCountModel. + // + // This also takes into account that there might be + // some tickets with the same product id, but different names. + TicketCountModel mapper(MapEntry> entry) { + final MapEntry(key: id, value: tickets) = entry; + // If there are multiple ticket names present, join them with a slash. + final ticketName = tickets.map((t) => t.productName).toSet().join('/'); + + return TicketCountModel( + count: tickets.length, + productName: ticketName, + productId: id, + ); + } + + return executor + .execute(() => apiV2.apiV2TicketsGet(includeUsed: false)) + .map( + (result) => result + .groupListsBy((t) => t.productId) + .entries + .map(mapper) + .sortedBy((t) => t.productId), + ); } - Future> useTicket(int productId) async { - return executor.executeAndMap( - () => apiV1.apiV1TicketsUsePost(body: UseTicketDTO(productId: productId)), - SwipeReceiptModel.fromTicketDto, - ); + Future> useTicket(int productId) { + final body = UseTicketDTO(productId: productId); + return executor + .execute(() => apiV1.apiV1TicketsUsePost(body: body)) + .map(SwipeReceiptModel.fromTicketDto); } } diff --git a/lib/features/user/data/datasources/user_remote_data_source.dart b/lib/features/user/data/datasources/user_remote_data_source.dart index f489aedd6..b9d98b4f2 100644 --- a/lib/features/user/data/datasources/user_remote_data_source.dart +++ b/lib/features/user/data/datasources/user_remote_data_source.dart @@ -16,34 +16,29 @@ class UserRemoteDataSource { /// Get the currently logged in user. Future> getUser() { - return executor.executeAndMap( - apiV2.apiV2AccountGet, - UserModel.fromResponse, - ); + return executor.execute(apiV2.apiV2AccountGet).map(UserModel.fromResponse); } /// Updates the details of the currently logged in user based on /// the non-null details in [user] Future> updateUserDetails(UpdateUser user) { - return executor.executeAndMap( - () => apiV2.apiV2AccountPut( - body: UpdateUserRequest( - name: user.name, - programmeId: user.occupationId, - email: user.email, - privacyActivated: user.privacyActivated, - password: user.encodedPasscode, - ), - ), - UserModel.fromResponse, - ); + return executor + .execute( + () => apiV2.apiV2AccountPut( + body: UpdateUserRequest( + name: user.name, + programmeId: user.occupationId, + email: user.email, + privacyActivated: user.privacyActivated, + password: user.encodedPasscode, + ), + ), + ) + .map(UserModel.fromResponse); } /// Request account deletion for the currently logged in user. - Future> requestAccountDeletion() async { - return executor.executeAndMap( - apiV2.apiV2AccountDelete, - (_) => unit, - ); + Future> requestAccountDeletion() { + return executor.executeAndDiscard(apiV2.apiV2AccountDelete); } } diff --git a/lib/features/user/domain/usecases/request_account_deletion.dart b/lib/features/user/domain/usecases/request_account_deletion.dart index e161c6a0f..fb9828c23 100644 --- a/lib/features/user/domain/usecases/request_account_deletion.dart +++ b/lib/features/user/domain/usecases/request_account_deletion.dart @@ -9,7 +9,7 @@ class RequestAccountDeletion implements UseCase { RequestAccountDeletion({required this.dataSource}); @override - Future> call(NoParams params) async { + Future> call(NoParams params) async { return dataSource.requestAccountDeletion(); } } diff --git a/lib/features/voucher/data/datasources/voucher_remote_data_source.dart b/lib/features/voucher/data/datasources/voucher_remote_data_source.dart index 03851daa9..5c7dcf8f8 100644 --- a/lib/features/voucher/data/datasources/voucher_remote_data_source.dart +++ b/lib/features/voucher/data/datasources/voucher_remote_data_source.dart @@ -17,9 +17,10 @@ class VoucherRemoteDataSource { Future> redeemVoucher( String voucher, ) { - return executor.executeAndMap( - () => apiV1.apiV1PurchasesRedeemvoucherPost(voucherCode: voucher), - RedeemedVoucherModel.fromDTO, - ); + return executor + .execute( + () => apiV1.apiV1PurchasesRedeemvoucherPost(voucherCode: voucher), + ) + .map(RedeemedVoucherModel.fromDTO); } } diff --git a/test/core/data/datasources/account_remote_data_source_test.dart b/test/core/data/datasources/account_remote_data_source_test.dart index f674c8686..0748451b9 100644 --- a/test/core/data/datasources/account_remote_data_source_test.dart +++ b/test/core/data/datasources/account_remote_data_source_test.dart @@ -42,7 +42,7 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); provideDummy>( @@ -141,8 +141,8 @@ void main() { group('requestPasscodeReset', () { test('should return [Right] when executor succeeds', () async { // arrange - when(executor.execute(any)).thenAnswer( - (_) async => Right(v1.MessageResponseDto()), + when(executor.executeAndDiscard(any)).thenAnswer( + (_) async => const Right(unit), ); // act diff --git a/test/core/network/network_request_executor_test.dart b/test/core/network/network_request_executor_test.dart index 978aae460..ec7207573 100644 --- a/test/core/network/network_request_executor_test.dart +++ b/test/core/network/network_request_executor_test.dart @@ -41,12 +41,8 @@ void main() { final testResponse = responseFromStatusCode(500, body: ''); // act - final actual = await executor.executeAndMap( - () async => testResponse, - identity, - ); -// TODO... MAKE SURE TO TRANSFORM ALL THE EXECUTOR CALLS TO CALLANDMAPLIST WHERE -// IT IS APPROPRIATE + final actual = await executor.execute(() async => testResponse); + // assert expect(actual, const Left(ServerFailure(Strings.unknownErrorOccured))); }); @@ -62,7 +58,7 @@ void main() { expect(actual, const Right('some string')); }); - test('should return [ServerFailure] if call throws [Exception]', () async { + test('should return [ConnectionFailure] if exception is caught', () async { // arrange final testException = Exception('some error'); @@ -71,6 +67,5 @@ void main() { // assert expect(actual, const Left(ConnectionFailure())); - // FIXME: this is not the correct error message }); } diff --git a/test/features/opening_hours/domain/usecases/check_open_status_test.dart b/test/features/opening_hours/domain/usecases/check_open_status_test.dart index 5d4391035..58d130f58 100644 --- a/test/features/opening_hours/domain/usecases/check_open_status_test.dart +++ b/test/features/opening_hours/domain/usecases/check_open_status_test.dart @@ -26,7 +26,6 @@ void main() { test('should call data source', () async { // arrange when(dataSource.isOpen()).thenAnswer((_) async => const Right(true)); - // TODO(marfavi): change thenAnswer to thenReturn // act await getIsOpen(NoParams()); diff --git a/test/features/register/data/datasources/register_remote_data_source_test.dart b/test/features/register/data/datasources/register_remote_data_source_test.dart index e232c94c7..7fdce03a0 100644 --- a/test/features/register/data/datasources/register_remote_data_source_test.dart +++ b/test/features/register/data/datasources/register_remote_data_source_test.dart @@ -26,13 +26,14 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); + provideDummy>(const Left(ConnectionFailure())); }); group('register', () { test('should call executor', () async { // arrange - when(executor.execute(any)).thenAnswer( - (_) async => Right(MessageResponseDto()), + when(executor.executeAndDiscard(any)).thenAnswer( + (_) async => const Right(unit), ); // act @@ -44,7 +45,7 @@ void main() { ); // assert - verify(executor.execute(any)).called(1); + verify(executor.executeAndDiscard(any)).called(1); }); }); } diff --git a/test/features/user/data/datasources/user_remote_data_source_test.dart b/test/features/user/data/datasources/user_remote_data_source_test.dart index 719e44216..a39f2fe3a 100644 --- a/test/features/user/data/datasources/user_remote_data_source_test.dart +++ b/test/features/user/data/datasources/user_remote_data_source_test.dart @@ -38,9 +38,6 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( - const Left(ConnectionFailure()), - ); }); const testUserModel = UserModel( @@ -146,7 +143,7 @@ void main() { group('requestAccountDeletion', () { test('should return [Right] if executor succeeds', () async { // arrange - when(executor.execute(any)).thenAnswer( + when(executor.executeAndDiscard(any)).thenAnswer( (_) async => const Right(unit), ); @@ -159,7 +156,7 @@ void main() { test('should return [Left] if executor fails', () async { // arrange - when(executor.execute(any)).thenAnswer( + when(executor.executeAndDiscard(any)).thenAnswer( (_) async => const Left(ServerFailure('some error')), ); diff --git a/test/features/user/presentation/cubit/user_cubit_test.dart b/test/features/user/presentation/cubit/user_cubit_test.dart index 2ffe9082a..f0b5c51a2 100644 --- a/test/features/user/presentation/cubit/user_cubit_test.dart +++ b/test/features/user/presentation/cubit/user_cubit_test.dart @@ -35,7 +35,7 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( + provideDummy>( const Left(ConnectionFailure()), ); }); diff --git a/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart b/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart index 3514f9932..2abd304d1 100644 --- a/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart +++ b/test/features/voucher/data/datasources/voucher_remote_data_source_test.dart @@ -2,7 +2,6 @@ import 'package:coffeecard/core/errors/failures.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; import 'package:coffeecard/features/voucher/data/datasources/voucher_remote_data_source.dart'; import 'package:coffeecard/features/voucher/data/models/redeemed_voucher_model.dart'; -import 'package:coffeecard/features/voucher/domain/entities/redeemed_voucher.dart'; import 'package:coffeecard/generated/api/coffeecard_api.swagger.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fpdart/fpdart.dart'; @@ -13,14 +12,15 @@ import 'voucher_remote_data_source_test.mocks.dart'; @GenerateNiceMocks([ MockSpec(), + MockSpec(), ]) void main() { late VoucherRemoteDataSource remoteDataSource; late MockCoffeecardApi apiV1; - late NetworkRequestExecutor executor; + late MockNetworkRequestExecutor executor; setUp(() { - executor = NetworkRequestExecutor(); + executor = MockNetworkRequestExecutor(); apiV1 = MockCoffeecardApi(); remoteDataSource = VoucherRemoteDataSource( apiV1: apiV1, @@ -30,9 +30,6 @@ void main() { provideDummy>( const Left(ConnectionFailure()), ); - provideDummy>( - const Left(ConnectionFailure()), - ); }); group('redeemVoucher', () { @@ -58,7 +55,7 @@ void main() { final actual = await remoteDataSource.redeemVoucher('voucher'); // assert - verify(executor.execute(any)); + verify(executor.execute(any)).called(1); expect( actual, const Right(