[in_app_purchase] refactoring and tests (#1322)

Updated retrieveReceiptData to be a static method.
Added missing SK prefix for certain store kit wrapper classes.
Added an iOS Platform stub to unit test all the method channel apis.
Removed tests that were duplicates after introducing the iOS Platform stub.
Refactored test code to be more compact.
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
index 53a9d0e..d9a1e45 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'package:collection/collection.dart';
 import 'package:flutter/foundation.dart';
 import 'package:in_app_purchase/src/channel.dart';
 import 'package:json_annotation/json_annotation.dart';
@@ -214,7 +215,7 @@
   /// Indicates the transaction is being processed in App Store.
   ///
   /// You should update your UI to indicate the process and waiting for the transaction to update to the next state.
-  /// Never complte a transaction that is in purchasing state.
+  /// Never complete a transaction that is in purchasing state.
   @JsonValue(0)
   purchasing,
 
@@ -302,6 +303,24 @@
 
   /// The error object, only available if the [transactionState] is [SKPaymentTransactionStateWrapper.failed].
   final SKError error;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKPaymentTransactionWrapper typedOther = other;
+    return typedOther.payment == payment &&
+        typedOther.transactionState == transactionState &&
+        typedOther.originalTransaction == originalTransaction &&
+        typedOther.transactionTimeStamp == transactionTimeStamp &&
+        typedOther.transactionIdentifier == transactionIdentifier &&
+        DeepCollectionEquality().equals(typedOther.downloads, downloads) &&
+        typedOther.error == error;
+  }
 }
 
 /// Dart wrapper around StoreKit's [SKDownloadState](https://developer.apple.com/documentation/storekit/skdownloadstate?language=objc).
@@ -403,6 +422,27 @@
 
   /// The error that prevented the downloading; only available if the [transactionState] is [SKPaymentTransactionStateWrapper.failed].
   final SKError error;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKDownloadWrapper typedOther = other;
+    return typedOther.contentIdentifier == contentIdentifier &&
+        typedOther.state == state &&
+        typedOther.contentLength == contentLength &&
+        typedOther.contentURL == contentURL &&
+        typedOther.contentVersion == contentVersion &&
+        typedOther.transactionID == transactionID &&
+        typedOther.progress == progress &&
+        typedOther.timeRemaining == timeRemaining &&
+        typedOther.downloadTimeUnknown == downloadTimeUnknown &&
+        typedOther.error == error;
+  }
 }
 
 /// Dart wrapper around StoreKit's [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc).
@@ -430,6 +470,21 @@
 
   /// A map that contains more detailed information about the error. Any key of the map must be one of the [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc).
   final Map<String, dynamic> userInfo;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKError typedOther = other;
+    return typedOther.code == code &&
+        typedOther.domain == domain &&
+        DeepCollectionEquality.unordered()
+            .equals(typedOther.userInfo, userInfo);
+  }
 }
 
 /// Dart wrapper around StoreKit's [SKPayment](https://developer.apple.com/documentation/storekit/skpayment?language=objc).
@@ -474,7 +529,7 @@
   /// An opaque id for the user's account.
   ///
   /// Used to help the store detect irregular activity. See https://developer.apple.com/documentation/storekit/skpayment/1506116-applicationusername?language=objc for more details.
-  /// For example, you can use a one-way hash of the user’s account name on your server. Don’t use the Apple ID for your developer account, the user’s Apple ID, or the user’s unhashed account name on your server.
+  /// For example, you can use a one-way hash of the user’s account name on your server. Don’t use the Apple ID for your developer account, the user’s Apple ID, or the user’s not hashed account name on your server.
   final String applicationUsername;
 
   /// Reserved for future use.
@@ -493,4 +548,20 @@
   ///
   /// For how to test in App Store sand box, see https://developer.apple.com/in-app-purchase/.
   final bool simulatesAskToBuyInSandbox;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKPaymentWrapper typedOther = other;
+    return typedOther.productIdentifier == productIdentifier &&
+        typedOther.applicationUsername == applicationUsername &&
+        typedOther.quantity == quantity &&
+        typedOther.simulatesAskToBuyInSandbox == simulatesAskToBuyInSandbox &&
+        typedOther.requestData == requestData;
+  }
 }
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart
index ddc35bb..633f6b4 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/foundation.dart';
+import 'package:collection/collection.dart';
 import 'package:json_annotation/json_annotation.dart';
 import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart';
 
@@ -41,14 +42,28 @@
   /// found here https://developer.apple.com/documentation/storekit/skproductsresponse/1505985-invalidproductidentifiers?language=objc.
   /// Will be empty if all the product identifiers are valid.
   final List<String> invalidProductIdentifiers;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SkProductResponseWrapper typedOther = other;
+    return DeepCollectionEquality().equals(typedOther.products, products) &&
+        DeepCollectionEquality().equals(
+            typedOther.invalidProductIdentifiers, invalidProductIdentifiers);
+  }
 }
 
 /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc).
 ///
-/// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minium is a day and maxium is a year.
+/// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minimum is a day and maximum is a year.
 // The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition
 // in the [SKProductPeriodUnit], this need to be updated to match.
-enum SubscriptionPeriodUnit {
+enum SKSubscriptionPeriodUnit {
   @JsonValue(0)
   day,
   @JsonValue(1)
@@ -83,7 +98,19 @@
   final int numberOfUnits;
 
   /// The time unit used to specify the length of this period.
-  final SubscriptionPeriodUnit unit;
+  final SKSubscriptionPeriodUnit unit;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKProductSubscriptionPeriodWrapper typedOther = other;
+    return typedOther.numberOfUnits == numberOfUnits && typedOther.unit == unit;
+  }
 }
 
 /// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc).
@@ -91,7 +118,7 @@
 /// This is used as a property in the [SKProductDiscountWrapper].
 // The values of the enum options are matching the [SKProductDiscountPaymentMode]'s values. Should there be an update or addition
 // in the [SKProductDiscountPaymentMode], this need to be updated to match.
-enum ProductDiscountPaymentMode {
+enum SKProductDiscountPaymentMode {
   /// Allows user to pay the discounted price at each payment period.
   @JsonValue(0)
   payAsYouGo,
@@ -130,7 +157,7 @@
   final String price;
 
   /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale.
-  final PriceLocaleWrapper priceLocale;
+  final SKPriceLocaleWrapper priceLocale;
 
   /// The object represent the discount period length.
   ///
@@ -138,13 +165,29 @@
   final int numberOfPeriods;
 
   /// The object indicates how the discount price is charged.
-  final ProductDiscountPaymentMode paymentMode;
+  final SKProductDiscountPaymentMode paymentMode;
 
   /// The object represents the duration of single subscription period for the discount.
   ///
   /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod],
   /// and their units and duration do not have to be matched.
   final SKProductSubscriptionPeriodWrapper subscriptionPeriod;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKProductDiscountWrapper typedOther = other;
+    return typedOther.price == price &&
+        typedOther.priceLocale == priceLocale &&
+        typedOther.numberOfPeriods == numberOfPeriods &&
+        typedOther.paymentMode == paymentMode &&
+        typedOther.subscriptionPeriod == subscriptionPeriod;
+  }
 }
 
 /// Dart wrapper around StoreKit's [SKProduct](https://developer.apple.com/documentation/storekit/skproduct?language=objc).
@@ -190,7 +233,7 @@
   final String localizedDescription;
 
   /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale.
-  final PriceLocaleWrapper priceLocale;
+  final SKPriceLocaleWrapper priceLocale;
 
   /// The version of the downloadable content.
   ///
@@ -226,12 +269,35 @@
   /// The object represents the duration of single subscription period.
   ///
   /// This is only available if you set up the introductory price in the App Store Connect, otherwise it will be null.
-  /// Programmar is also responsible to determine if the user is eligible to receive it. See https://developer.apple.com/documentation/storekit/in-app_purchase/offering_introductory_pricing_in_your_app?language=objc
+  /// Programmer is also responsible to determine if the user is eligible to receive it. See https://developer.apple.com/documentation/storekit/in-app_purchase/offering_introductory_pricing_in_your_app?language=objc
   /// for more details.
   /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod],
   /// and their units and duration do not have to be matched.
   final SKProductDiscountWrapper introductoryPrice;
 
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKProductWrapper typedOther = other;
+    return typedOther.productIdentifier == productIdentifier &&
+        typedOther.localizedTitle == localizedTitle &&
+        typedOther.localizedDescription == localizedDescription &&
+        typedOther.priceLocale == priceLocale &&
+        typedOther.downloadContentVersion == downloadContentVersion &&
+        typedOther.subscriptionGroupIdentifier == subscriptionGroupIdentifier &&
+        typedOther.price == price &&
+        typedOther.downloadable == downloadable &&
+        DeepCollectionEquality.unordered().equals(
+            typedOther.downloadContentLengths, downloadContentLengths) &&
+        typedOther.subscriptionPeriod == subscriptionPeriod &&
+        typedOther.introductoryPrice == introductoryPrice;
+  }
+
   /// Method to convert to the wrapper to the consolidated [ProductDetails] class.
   ProductDetails toProductDetails() {
     return ProductDetails(
@@ -250,18 +316,30 @@
 //                 Matching android to only get the currencySymbol for now.
 //                 https://github.com/flutter/flutter/issues/26610
 @JsonSerializable()
-class PriceLocaleWrapper {
-  PriceLocaleWrapper({@required this.currencySymbol});
+class SKPriceLocaleWrapper {
+  SKPriceLocaleWrapper({@required this.currencySymbol});
 
   /// Constructing an instance from a map from the Objective-C layer.
   ///
   /// This method should only be used with `map` values returned by [SKProductWrapper.fromJson] and [SKProductDiscountWrapper.fromJson].
   /// The `map` parameter must not be null.
-  factory PriceLocaleWrapper.fromJson(Map map) {
+  factory SKPriceLocaleWrapper.fromJson(Map map) {
     assert(map != null, 'Map must not be null.');
     return _$PriceLocaleWrapperFromJson(map);
   }
 
   ///The currency symbol for the locale, e.g. $ for US locale.
   final String currencySymbol;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKPriceLocaleWrapper typedOther = other;
+    return typedOther.currencySymbol == currencySymbol;
+  }
 }
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart
index d6392d8..0489540 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart
@@ -44,11 +44,11 @@
   return _$enumDecode<T>(enumValues, source);
 }
 
-const _$SubscriptionPeriodUnitEnumMap = <SubscriptionPeriodUnit, dynamic>{
-  SubscriptionPeriodUnit.day: 0,
-  SubscriptionPeriodUnit.week: 1,
-  SubscriptionPeriodUnit.month: 2,
-  SubscriptionPeriodUnit.year: 3
+const _$SubscriptionPeriodUnitEnumMap = <SKSubscriptionPeriodUnit, dynamic>{
+  SKSubscriptionPeriodUnit.day: 0,
+  SKSubscriptionPeriodUnit.week: 1,
+  SKSubscriptionPeriodUnit.month: 2,
+  SKSubscriptionPeriodUnit.year: 3
 };
 
 SKProductDiscountWrapper _$SKProductDiscountWrapperFromJson(Map json) {
@@ -56,7 +56,7 @@
       price: json['price'] as String,
       priceLocale: json['priceLocale'] == null
           ? null
-          : PriceLocaleWrapper.fromJson(json['priceLocale'] as Map),
+          : SKPriceLocaleWrapper.fromJson(json['priceLocale'] as Map),
       numberOfPeriods: json['numberOfPeriods'] as int,
       paymentMode: _$enumDecodeNullable(
           _$ProductDiscountPaymentModeEnumMap, json['paymentMode']),
@@ -67,10 +67,10 @@
 }
 
 const _$ProductDiscountPaymentModeEnumMap =
-    <ProductDiscountPaymentMode, dynamic>{
-  ProductDiscountPaymentMode.payAsYouGo: 0,
-  ProductDiscountPaymentMode.payUpFront: 1,
-  ProductDiscountPaymentMode.freeTrail: 2
+    <SKProductDiscountPaymentMode, dynamic>{
+  SKProductDiscountPaymentMode.payAsYouGo: 0,
+  SKProductDiscountPaymentMode.payUpFront: 1,
+  SKProductDiscountPaymentMode.freeTrail: 2
 };
 
 SKProductWrapper _$SKProductWrapperFromJson(Map json) {
@@ -80,7 +80,7 @@
       localizedDescription: json['localizedDescription'] as String,
       priceLocale: json['priceLocale'] == null
           ? null
-          : PriceLocaleWrapper.fromJson(json['priceLocale'] as Map),
+          : SKPriceLocaleWrapper.fromJson(json['priceLocale'] as Map),
       downloadContentVersion: json['downloadContentVersion'] as String,
       subscriptionGroupIdentifier:
           json['subscriptionGroupIdentifier'] as String,
@@ -99,6 +99,6 @@
               json['introductoryPrice'] as Map));
 }
 
-PriceLocaleWrapper _$PriceLocaleWrapperFromJson(Map json) {
-  return PriceLocaleWrapper(currencySymbol: json['currencySymbol'] as String);
+SKPriceLocaleWrapper _$PriceLocaleWrapperFromJson(Map json) {
+  return SKPriceLocaleWrapper(currencySymbol: json['currencySymbol'] as String);
 }
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart
index eed09f1..7608275 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart
@@ -14,7 +14,7 @@
   /// There are 2 ways to do so. Either validate locally or validate with App Store.
   /// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1).
   /// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt.
-  Future<String> retrieveReceiptData() {
+  static Future<String> retrieveReceiptData() {
     return channel
         .invokeMethod('-[InAppPurchasePlugin retrieveReceiptData:result:]');
   }
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart
index 353d34e..43fee64 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart
@@ -15,8 +15,8 @@
 class SKRequestMaker {
   /// Fetches product information for a list of given product identifiers.
   ///
-  /// The `productIdentifiers` should contain legit product identifiers that you declared for the products in the Itunes Connect. invalid identifiers
-  /// Will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted.
+  /// The `productIdentifiers` should contain legitimate product identifiers that you declared for the products in the iTunes Connect. Invalid identifiers
+  /// will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted.
   /// If `productIdentifiers` is null, an `storekit_invalid_argument` error will be returned. If `productIdentifiers` is empty, a [SkProductResponseWrapper]
   /// will still be returned with [SkProductResponseWrapper.products] being null.
   ///
diff --git a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart
index 3d2ec3f..aba9d89 100644
--- a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart
+++ b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart
@@ -8,6 +8,7 @@
 import 'package:in_app_purchase/src/in_app_purchase_connection/app_store_connection.dart';
 import '../stub_in_app_purchase_platform.dart';
 import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart';
+import '../store_kit_wrappers/sk_test_stub_objects.dart';
 
 void main() {
   final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform();
@@ -29,47 +30,14 @@
     });
   });
 
-  final Map<String, dynamic> localeMap = <String, dynamic>{
-    'currencySymbol': '\$'
-  };
-  final Map<String, dynamic> subMap = <String, dynamic>{
-    'numberOfUnits': 1,
-    'unit': 2
-  };
-  final Map<String, dynamic> discountMap = <String, dynamic>{
-    'price': '1.0',
-    'priceLocale': localeMap,
-    'numberOfPeriods': 1,
-    'paymentMode': 2,
-    'subscriptionPeriod': subMap,
-  };
-  final Map<String, dynamic> productMap = <String, dynamic>{
-    'productIdentifier': 'id',
-    'localizedTitle': 'splash coin',
-    'localizedDescription': 'description',
-    'priceLocale': localeMap,
-    'downloadContentVersion': 'version',
-    'subscriptionGroupIdentifier': 'com.group',
-    'price': '1.0',
-    'downloadable': true,
-    'downloadContentLengths': <int>[1, 2],
-    'subscriptionPeriod': subMap,
-    'introductoryPrice': discountMap,
-  };
-
-  final Map<String, List<dynamic>> productResponseMap = <String, List<dynamic>>{
-    'products': <Map<String, dynamic>>[productMap],
-    'invalidProductIdentifiers': <String>['567'],
-  };
-
   group('query product list', () {
     test('should get product list', () async {
       stubPlatform.addResponse(
           name: '-[InAppPurchasePlugin startProductRequest:result:]',
-          value: productResponseMap);
+          value: buildProductResponseMap(dummyProductResponseWrapper));
       final AppStoreConnection connection = AppStoreConnection();
       final ProductDetailsResponse response =
-          await connection.queryProductDetails(<String>['123'].toSet());
+          await connection.queryProductDetails(<String>['id'].toSet());
       List<ProductDetails> products = response.productDetails;
       expect(
         products,
@@ -77,7 +45,7 @@
       );
       expect(
         products.first.title,
-        'splash coin',
+        'title',
       );
       expect(
         products.first.title,
@@ -88,13 +56,13 @@
     test('should get correct not found identifiers', () async {
       stubPlatform.addResponse(
           name: '-[InAppPurchasePlugin startProductRequest:result:]',
-          value: productResponseMap);
+          value: buildProductResponseMap(dummyProductResponseWrapper));
       final AppStoreConnection connection = AppStoreConnection();
       final ProductDetailsResponse response =
           await connection.queryProductDetails(<String>['123'].toSet());
       expect(
         response.notFoundIDs,
-        <String>['567'],
+        <String>['123'],
       );
     });
   });
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
new file mode 100644
index 0000000..75873dd
--- /dev/null
+++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
@@ -0,0 +1,186 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:in_app_purchase/src/channel.dart';
+import 'package:in_app_purchase/store_kit_wrappers.dart';
+import 'sk_test_stub_objects.dart';
+
+void main() {
+  final FakeIOSPlatform fakeIOSPlatform = FakeIOSPlatform();
+
+  setUpAll(() {
+    SystemChannels.platform
+        .setMockMethodCallHandler(fakeIOSPlatform.onMethodCall);
+  });
+
+  setUp(() {});
+
+  group('sk_request_maker', () {
+    test('get products method channel', () async {
+      SkProductResponseWrapper productResponseWrapper =
+          await SKRequestMaker().startProductRequest(['xxx']);
+      expect(
+        productResponseWrapper.products,
+        isNotEmpty,
+      );
+      expect(
+        productResponseWrapper.products.first.priceLocale.currencySymbol,
+        '\$',
+      );
+      expect(
+        productResponseWrapper.products.first.priceLocale.currencySymbol,
+        isNot('A'),
+      );
+      expect(
+        productResponseWrapper.invalidProductIdentifiers,
+        isNotEmpty,
+      );
+
+      expect(
+        fakeIOSPlatform.startProductRequestParam,
+        ['xxx'],
+      );
+    });
+
+    test('get products method channel should throw exception', () async {
+      fakeIOSPlatform.getProductRequestFailTest = true;
+      expect(
+        SKRequestMaker().startProductRequest(['xxx']),
+        throwsException,
+      );
+      fakeIOSPlatform.getProductRequestFailTest = false;
+    });
+
+    test('refreshed receipt', () async {
+      int receiptCountBefore = fakeIOSPlatform.refreshReceipt;
+      await SKRequestMaker()
+          .startRefreshReceiptRequest(receiptProperties: {"isExpired": true});
+      expect(fakeIOSPlatform.refreshReceipt, receiptCountBefore + 1);
+      expect(fakeIOSPlatform.refreshReceiptParam, {"isExpired": true});
+    });
+  });
+
+  group('sk_receipt_manager', () {
+    test('should get receipt (faking it by returning a `receipt data` string)',
+        () async {
+      String receiptData = await SKReceiptManager.retrieveReceiptData();
+      expect(receiptData, 'receipt data');
+    });
+  });
+
+  group('sk_payment_queue', () {
+    test('canMakePayment should return true', () async {
+      expect(await SKPaymentQueueWrapper.canMakePayments(), true);
+    });
+
+    test(
+        'throws if observer is not set for payment queue before adding payment',
+        () async {
+      expect(SKPaymentQueueWrapper().addPayment(dummyPayment),
+          throwsAssertionError);
+    });
+
+    test('should add payment to the payment queue', () async {
+      SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
+      TestPaymentTransactionObserver observer =
+          TestPaymentTransactionObserver();
+      queue.setTransactionObserver(observer);
+      await queue.addPayment(dummyPayment);
+      expect(fakeIOSPlatform.payments.first, equals(dummyPayment));
+    });
+
+    test('should finish transaction', () async {
+      SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
+      TestPaymentTransactionObserver observer =
+          TestPaymentTransactionObserver();
+      queue.setTransactionObserver(observer);
+      await queue.finishTransaction(dummyTransaction);
+      expect(fakeIOSPlatform.transactionsFinished.first,
+          equals(dummyTransaction.transactionIdentifier));
+    });
+
+    test('should restore transaction', () async {
+      SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
+      TestPaymentTransactionObserver observer =
+          TestPaymentTransactionObserver();
+      queue.setTransactionObserver(observer);
+      await queue.restoreTransactions(applicationUserName: 'aUserID');
+      expect(fakeIOSPlatform.applicationNameHasTransactionRestored, 'aUserID');
+    });
+  });
+}
+
+class FakeIOSPlatform {
+  FakeIOSPlatform() {
+    channel.setMockMethodCallHandler(onMethodCall);
+    getProductRequestFailTest = false;
+  }
+  // get product request
+  List startProductRequestParam;
+  bool getProductRequestFailTest;
+
+  // refresh receipt request
+  int refreshReceipt = 0;
+  Map refreshReceiptParam;
+
+  // payment queue
+  List<SKPaymentWrapper> payments = [];
+  List<String> transactionsFinished = [];
+  String applicationNameHasTransactionRestored;
+
+  Future<dynamic> onMethodCall(MethodCall call) {
+    switch (call.method) {
+      // request makers
+      case '-[InAppPurchasePlugin startProductRequest:result:]':
+        List<String> productIDS =
+            List.castFrom<dynamic, String>(call.arguments);
+        assert(productIDS is List<String>, 'invalid argument type');
+        startProductRequestParam = call.arguments;
+        if (getProductRequestFailTest) {
+          return Future<Map<String, dynamic>>.value(null);
+        }
+        return Future<Map<String, dynamic>>.value(
+            buildProductResponseMap(dummyProductResponseWrapper));
+      case '-[InAppPurchasePlugin refreshReceipt:result:]':
+        refreshReceipt++;
+        refreshReceiptParam = call.arguments;
+        return Future<void>.sync(() {});
+      // receipt manager
+      case '-[InAppPurchasePlugin retrieveReceiptData:result:]':
+        return Future<String>.value('receipt data');
+      // payment queue
+      case '-[SKPaymentQueue canMakePayments:]':
+        return Future<bool>.value(true);
+      case '-[InAppPurchasePlugin addPayment:result:]':
+        payments.add(SKPaymentWrapper.fromJson(call.arguments));
+        return Future<void>.sync(() {});
+      case '-[InAppPurchasePlugin finishTransaction:result:]':
+        transactionsFinished.add(call.arguments);
+        return Future<void>.sync(() {});
+      case '-[InAppPurchasePlugin restoreTransactions:result:]':
+        applicationNameHasTransactionRestored = call.arguments;
+        return Future<void>.sync(() {});
+    }
+    return Future<void>.sync(() {});
+  }
+}
+
+class TestPaymentTransactionObserver extends SKTransactionObserverWrapper {
+  void updatedTransactions({List<SKPaymentTransactionWrapper> transactions}) {}
+
+  void removedTransactions({List<SKPaymentTransactionWrapper> transactions}) {}
+
+  void restoreCompletedTransactions({Error error}) {}
+
+  void paymentQueueRestoreCompletedTransactionsFinished() {}
+
+  void updatedDownloads({List<SKDownloadWrapper> downloads}) {}
+
+  bool shouldAddStorePayment(
+      {SKPaymentWrapper payment, SKProductWrapper product}) {
+    return true;
+  }
+}
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart
deleted file mode 100644
index fc342db..0000000
--- a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:test/test.dart';
-
-import 'package:in_app_purchase/store_kit_wrappers.dart';
-import 'package:in_app_purchase/src/channel.dart';
-import '../stub_in_app_purchase_platform.dart';
-
-void main() {
-  final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform();
-  final dummyPayment = SKPaymentWrapper(
-      productIdentifier: 'prod-id',
-      applicationUsername: 'app-user-name',
-      requestData: 'fake-data-utf8',
-      quantity: 2,
-      simulatesAskToBuyInSandbox: true);
-  final SKError dummyError =
-      SKError(code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'});
-  final SKDownloadWrapper dummyDownload = SKDownloadWrapper(
-    contentIdentifier: 'id',
-    state: SKDownloadState.failed,
-    contentLength: 32,
-    contentURL: 'https://download.com',
-    contentVersion: '0.0.1',
-    transactionID: 'tranID',
-    progress: 0.6,
-    timeRemaining: 1231231,
-    downloadTimeUnknown: false,
-    error: dummyError,
-  );
-  final SKPaymentTransactionWrapper dummyOriginalTransaction =
-      SKPaymentTransactionWrapper(
-    transactionState: SKPaymentTransactionStateWrapper.purchased,
-    payment: dummyPayment,
-    originalTransaction: null,
-    transactionTimeStamp: 1231231231.00,
-    transactionIdentifier: '123123',
-    downloads: [dummyDownload],
-    error: dummyError,
-  );
-  final SKPaymentTransactionWrapper dummyTransaction =
-      SKPaymentTransactionWrapper(
-    transactionState: SKPaymentTransactionStateWrapper.purchased,
-    payment: dummyPayment,
-    originalTransaction: dummyOriginalTransaction,
-    transactionTimeStamp: 1231231231.00,
-    transactionIdentifier: '123123',
-    downloads: [dummyDownload],
-    error: dummyError,
-  );
-
-  setUpAll(() =>
-      channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler));
-
-  group('canMakePayments', () {
-    test('YES', () async {
-      stubPlatform.addResponse(
-          name: '-[SKPaymentQueue canMakePayments:]', value: true);
-      expect(await SKPaymentQueueWrapper.canMakePayments(), isTrue);
-    });
-
-    test('NO', () async {
-      stubPlatform.addResponse(
-          name: '-[SKPaymentQueue canMakePayments:]', value: false);
-      expect(await SKPaymentQueueWrapper.canMakePayments(), isFalse);
-    });
-  });
-
-  group('Wrapper fromJson tests', () {
-    test('Should construct correct SKPaymentWrapper from json', () {
-      SKPaymentWrapper payment =
-          SKPaymentWrapper.fromJson(dummyPayment.toMap());
-      testPayment(payment, dummyPayment);
-    });
-
-    test('Should construct correct SKError from json', () {
-      SKError error = SKError.fromJson(buildErrorMap(dummyError));
-      testSKError(error, dummyError);
-    });
-
-    test('Should construct correct SKDownloadWrapper from json', () {
-      SKDownloadWrapper download =
-          SKDownloadWrapper.fromJson(buildDownloadMap(dummyDownload));
-      testDownload(download, dummyDownload);
-    });
-
-    test('Should construct correct SKTransactionWrapper from json', () {
-      SKPaymentTransactionWrapper transaction =
-          SKPaymentTransactionWrapper.fromJson(
-              buildTransactionMap(dummyTransaction));
-      testTransaction(transaction, dummyTransaction);
-      if (transaction.originalTransaction != null) {
-        testTransaction(transaction.originalTransaction,
-            dummyTransaction.originalTransaction);
-      }
-    });
-
-    test('Should generate correct map of the payment object', () {
-      Map map = dummyPayment.toMap();
-      expect(map['productIdentifier'], dummyPayment.productIdentifier);
-      expect(map['applicationUsername'], dummyPayment.applicationUsername);
-
-      expect(map['requestData'], dummyPayment.requestData);
-
-      expect(map['quantity'], dummyPayment.quantity);
-
-      expect(map['simulatesAskToBuyInSandbox'],
-          dummyPayment.simulatesAskToBuyInSandbox);
-    });
-  });
-}
-
-Map<String, dynamic> buildErrorMap(SKError error) {
-  return {
-    'code': error.code,
-    'domain': error.domain,
-    'userInfo': error.userInfo,
-  };
-}
-
-Map<String, dynamic> buildDownloadMap(SKDownloadWrapper download) {
-  return {
-    'contentIdentifier': download.contentIdentifier,
-    'state': SKDownloadState.values.indexOf(download.state),
-    'contentLength': download.contentLength,
-    'contentURL': download.contentURL,
-    'contentVersion': download.contentVersion,
-    'transactionID': download.transactionID,
-    'progress': download.progress,
-    'timeRemaining': download.timeRemaining,
-    'downloadTimeUnknown': download.downloadTimeUnknown,
-    'error': buildErrorMap(download.error)
-  };
-}
-
-Map<String, dynamic> buildTransactionMap(
-    SKPaymentTransactionWrapper transaction) {
-  if (transaction == null) {
-    return null;
-  }
-  Map map = <String, dynamic>{
-    'transactionState': SKPaymentTransactionStateWrapper.values
-        .indexOf(SKPaymentTransactionStateWrapper.purchased),
-    'payment': transaction.payment.toMap(),
-    'originalTransaction': buildTransactionMap(transaction.originalTransaction),
-    'transactionTimeStamp': transaction.transactionTimeStamp,
-    'transactionIdentifier': transaction.transactionIdentifier,
-    'error': buildErrorMap(transaction.error),
-  };
-  List downloadList = transaction.downloads.map((SKDownloadWrapper download) {
-    return buildDownloadMap(download);
-  }).toList();
-  map['downloads'] = downloadList;
-  return map;
-}
-
-void testSKError(SKError error, SKError dummyError) {
-  expect(error.code, dummyError.code);
-  expect(error.domain, dummyError.domain);
-  expect(error.userInfo, dummyError.userInfo);
-}
-
-void testDownload(SKDownloadWrapper download, SKDownloadWrapper dummyDownload) {
-  expect(download.contentIdentifier, dummyDownload.contentIdentifier);
-  expect(download.state, dummyDownload.state);
-  expect(download.contentLength, dummyDownload.contentLength);
-  expect(download.contentURL, dummyDownload.contentURL);
-  expect(download.contentVersion, dummyDownload.contentVersion);
-  expect(download.transactionID, dummyDownload.transactionID);
-  expect(download.progress, dummyDownload.progress);
-  expect(download.timeRemaining, dummyDownload.timeRemaining);
-  expect(download.downloadTimeUnknown, dummyDownload.downloadTimeUnknown);
-  expect(download.error.code, dummyDownload.error.code);
-  expect(download.error.domain, dummyDownload.error.domain);
-  expect(download.error.userInfo, dummyDownload.error.userInfo);
-}
-
-void testPayment(SKPaymentWrapper payment, SKPaymentWrapper dummyPayment) {
-  expect(payment.productIdentifier, dummyPayment.productIdentifier);
-  expect(payment.applicationUsername, dummyPayment.applicationUsername);
-  expect(payment.requestData, dummyPayment.requestData);
-  expect(payment.quantity, dummyPayment.quantity);
-  expect(payment.simulatesAskToBuyInSandbox,
-      dummyPayment.simulatesAskToBuyInSandbox);
-}
-
-void testTransaction(SKPaymentTransactionWrapper transaction,
-    SKPaymentTransactionWrapper dummyTransaction) {
-  // payment
-  SKPaymentWrapper payment = transaction.payment;
-  SKPaymentWrapper dummyPayment = dummyTransaction.payment;
-  testPayment(payment, dummyPayment);
-  //download
-  SKDownloadWrapper download = transaction.downloads.first;
-  SKDownloadWrapper dummyDownload = dummyTransaction.downloads.first;
-  testDownload(download, dummyDownload);
-  //error
-  SKError error = transaction.error;
-  SKError dummyError = dummyTransaction.error;
-  testSKError(error, dummyError);
-
-  expect(transaction.transactionState, dummyTransaction.transactionState);
-  expect(
-      transaction.transactionTimeStamp, dummyTransaction.transactionTimeStamp);
-  expect(transaction.transactionIdentifier,
-      dummyTransaction.transactionIdentifier);
-}
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart
index cc806d0..107826f 100644
--- a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart
+++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart
@@ -5,54 +5,18 @@
 import 'package:test/test.dart';
 import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart';
 import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart';
+import 'package:in_app_purchase/store_kit_wrappers.dart';
+import 'sk_test_stub_objects.dart';
 
 void main() {
-  final Map<String, dynamic> localeMap = <String, dynamic>{
-    'currencySymbol': '\$'
-  };
-  final Map<String, dynamic> subMap = <String, dynamic>{
-    'numberOfUnits': 1,
-    'unit': 2
-  };
-  final Map<String, dynamic> discountMap = <String, dynamic>{
-    'price': '1.0',
-    'priceLocale': localeMap,
-    'numberOfPeriods': 1,
-    'paymentMode': 2,
-    'subscriptionPeriod': subMap,
-  };
-  final Map<String, dynamic> productMap = <String, dynamic>{
-    'productIdentifier': 'id',
-    'localizedTitle': 'title',
-    'localizedDescription': 'description',
-    'priceLocale': localeMap,
-    'downloadContentVersion': 'version',
-    'subscriptionGroupIdentifier': 'com.group',
-    'price': '1.0',
-    'downloadable': true,
-    'downloadContentLengths': <int>[1, 2],
-    'subscriptionPeriod': subMap,
-    'introductoryPrice': discountMap,
-  };
-
-  final Map<String, List<dynamic>> productResponseMap = <String, List<dynamic>>{
-    'products': <Map<String, dynamic>>[productMap],
-    'invalidProductIdentifiers': <String>['123'],
-  };
-
-  group('product request wrapper test', () {
-    void testMatchLocale(
-        PriceLocaleWrapper wrapper, Map<String, dynamic> localeMap) {
-      expect(wrapper.currencySymbol, localeMap['currencySymbol']);
-    }
-
+  group('product related object wrapper test', () {
     test(
         'SKProductSubscriptionPeriodWrapper should have property values consistent with map',
         () {
       final SKProductSubscriptionPeriodWrapper wrapper =
-          SKProductSubscriptionPeriodWrapper.fromJson(subMap);
-      expect(wrapper.numberOfUnits, subMap['numberOfUnits']);
-      expect(wrapper.unit, SubscriptionPeriodUnit.values[subMap['unit']]);
+          SKProductSubscriptionPeriodWrapper.fromJson(
+              buildSubscriptionPeriodMap(dummySubscription));
+      expect(wrapper, equals(dummySubscription));
     });
 
     test(
@@ -68,18 +32,8 @@
         'SKProductDiscountWrapper should have property values consistent with map',
         () {
       final SKProductDiscountWrapper wrapper =
-          SKProductDiscountWrapper.fromJson(discountMap);
-      expect(wrapper.price, discountMap['price']);
-      testMatchLocale(wrapper.priceLocale, discountMap['priceLocale']);
-      expect(wrapper.numberOfPeriods, discountMap['numberOfPeriods']);
-      expect(wrapper.paymentMode,
-          ProductDiscountPaymentMode.values[discountMap['paymentMode']]);
-      expect(
-          wrapper.subscriptionPeriod.unit,
-          SubscriptionPeriodUnit
-              .values[discountMap['subscriptionPeriod']['unit']]);
-      expect(wrapper.subscriptionPeriod.numberOfUnits,
-          discountMap['subscriptionPeriod']['numberOfUnits']);
+          SKProductDiscountWrapper.fromJson(buildDiscountMap(dummyDiscount));
+      expect(wrapper, equals(dummyDiscount));
     });
 
     test(
@@ -94,49 +48,11 @@
       expect(wrapper.subscriptionPeriod, null);
     });
 
-    void testMatchingProductMap(
-        SKProductWrapper wrapper, Map<String, dynamic> productMap) {
-      expect(wrapper.productIdentifier, productMap['productIdentifier']);
-      expect(wrapper.localizedTitle, productMap['localizedTitle']);
-      testMatchLocale(wrapper.priceLocale, productMap['priceLocale']);
-      expect(wrapper.localizedDescription, productMap['localizedDescription']);
-      expect(
-          wrapper.downloadContentVersion, productMap['downloadContentVersion']);
-      expect(wrapper.subscriptionGroupIdentifier,
-          productMap['subscriptionGroupIdentifier']);
-      expect(wrapper.price, productMap['price']);
-      expect(wrapper.downloadable, productMap['downloadable']);
-      expect(
-          wrapper.downloadContentLengths, productMap['downloadContentLengths']);
-      expect(wrapper.introductoryPrice.price,
-          productMap['introductoryPrice']['price']);
-      expect(wrapper.introductoryPrice.numberOfPeriods,
-          productMap['introductoryPrice']['numberOfPeriods']);
-      expect(
-          wrapper.introductoryPrice.paymentMode,
-          ProductDiscountPaymentMode
-              .values[productMap['introductoryPrice']['paymentMode']]);
-      expect(
-          wrapper.introductoryPrice.subscriptionPeriod.unit,
-          SubscriptionPeriodUnit.values[productMap['introductoryPrice']
-              ['subscriptionPeriod']['unit']]);
-      expect(
-          wrapper.introductoryPrice.subscriptionPeriod.numberOfUnits,
-          productMap['introductoryPrice']['subscriptionPeriod']
-              ['numberOfUnits']);
-      expect(
-          wrapper.subscriptionPeriod.unit,
-          SubscriptionPeriodUnit
-              .values[productMap['subscriptionPeriod']['unit']]);
-      expect(wrapper.subscriptionPeriod.numberOfUnits,
-          productMap['subscriptionPeriod']['numberOfUnits']);
-      expect(wrapper.price, discountMap['price']);
-    }
-
     test('SKProductWrapper should have property values consistent with map',
         () {
-      final SKProductWrapper wrapper = SKProductWrapper.fromJson(productMap);
-      testMatchingProductMap(wrapper, productMap);
+      final SKProductWrapper wrapper =
+          SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper));
+      expect(wrapper, equals(dummyProductWrapper));
     });
 
     test('SKProductWrapper should have properties to be null if map is empty',
@@ -155,7 +71,8 @@
     });
 
     test('toProductDetails() should return correct Product object', () {
-      final SKProductWrapper wrapper = SKProductWrapper.fromJson(productMap);
+      final SKProductWrapper wrapper =
+          SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper));
       final ProductDetails product = wrapper.toProductDetails();
       expect(product.title, wrapper.localizedTitle);
       expect(product.description, wrapper.localizedDescription);
@@ -166,11 +83,9 @@
 
     test('SKProductResponse wrapper should match', () {
       final SkProductResponseWrapper wrapper =
-          SkProductResponseWrapper.fromJson(productResponseMap);
-      testMatchingProductMap(
-          wrapper.products[0], productResponseMap['products'][0]);
-      expect(wrapper.invalidProductIdentifiers,
-          productResponseMap['invalidProductIdentifiers']);
+          SkProductResponseWrapper.fromJson(
+              buildProductResponseMap(dummyProductResponseWrapper));
+      expect(wrapper, equals(dummyProductResponseWrapper));
     });
     test('SKProductResponse wrapper should default to empty list', () {
       final Map<String, List<dynamic>> productResponseMapEmptyList =
@@ -185,8 +100,48 @@
     });
 
     test('LocaleWrapper should have property values consistent with map', () {
-      final PriceLocaleWrapper wrapper = PriceLocaleWrapper.fromJson(localeMap);
-      testMatchLocale(wrapper, localeMap);
+      final SKPriceLocaleWrapper wrapper =
+          SKPriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale));
+      expect(wrapper, equals(dummyLocale));
+    });
+  });
+
+  group('Payment queue related object tests', () {
+    test('Should construct correct SKPaymentWrapper from json', () {
+      SKPaymentWrapper payment =
+          SKPaymentWrapper.fromJson(dummyPayment.toMap());
+      expect(payment, equals(dummyPayment));
+    });
+
+    test('Should construct correct SKError from json', () {
+      SKError error = SKError.fromJson(buildErrorMap(dummyError));
+      expect(error, equals(dummyError));
+    });
+
+    test('Should construct correct SKDownloadWrapper from json', () {
+      SKDownloadWrapper download =
+          SKDownloadWrapper.fromJson(buildDownloadMap(dummyDownload));
+      expect(download, equals(dummyDownload));
+    });
+
+    test('Should construct correct SKTransactionWrapper from json', () {
+      SKPaymentTransactionWrapper transaction =
+          SKPaymentTransactionWrapper.fromJson(
+              buildTransactionMap(dummyTransaction));
+      expect(transaction, equals(dummyTransaction));
+    });
+
+    test('Should generate correct map of the payment object', () {
+      Map map = dummyPayment.toMap();
+      expect(map['productIdentifier'], dummyPayment.productIdentifier);
+      expect(map['applicationUsername'], dummyPayment.applicationUsername);
+
+      expect(map['requestData'], dummyPayment.requestData);
+
+      expect(map['quantity'], dummyPayment.quantity);
+
+      expect(map['simulatesAskToBuyInSandbox'],
+          dummyPayment.simulatesAskToBuyInSandbox);
     });
   });
 }
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart
deleted file mode 100644
index b35fd90..0000000
--- a/packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:test/test.dart';
-import 'package:in_app_purchase/src/store_kit_wrappers/sk_receipt_manager.dart';
-import 'package:in_app_purchase/src/channel.dart';
-
-import '../stub_in_app_purchase_platform.dart';
-
-void main() {
-  final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform();
-
-  setUpAll(() =>
-      channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler));
-
-  group('retrieveReceiptData', () {
-    test('Should get result', () async {
-      stubPlatform.addResponse(
-          name: '-[InAppPurchasePlugin retrieveReceiptData:result:]',
-          value: 'dummy data');
-      final String result = await SKReceiptManager().retrieveReceiptData();
-      expect(result, 'dummy data');
-    });
-  });
-}
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart
deleted file mode 100644
index 7bb6238..0000000
--- a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:test/test.dart';
-import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart';
-import 'package:in_app_purchase/src/channel.dart';
-import 'package:in_app_purchase/src/store_kit_wrappers/sk_request_maker.dart';
-import '../stub_in_app_purchase_platform.dart';
-
-void main() {
-  final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform();
-
-  final Map<String, dynamic> localeMap = <String, dynamic>{
-    'currencySymbol': '\$'
-  };
-  final Map<String, dynamic> subMap = <String, dynamic>{
-    'numberOfUnits': 1,
-    'unit': 2
-  };
-  final Map<String, dynamic> discountMap = <String, dynamic>{
-    'price': '1.0',
-    'priceLocale': localeMap,
-    'numberOfPeriods': 1,
-    'paymentMode': 2,
-    'subscriptionPeriod': subMap,
-  };
-  final Map<String, dynamic> productMap = <String, dynamic>{
-    'productIdentifier': 'id',
-    'localizedTitle': 'title',
-    'localizedDescription': 'description',
-    'priceLocale': localeMap,
-    'downloadContentVersion': 'version',
-    'subscriptionGroupIdentifier': 'com.group',
-    'price': '1.0',
-    'downloadable': true,
-    'downloadContentLengths': <int>[1, 2],
-    'subscriptionPeriod': subMap,
-    'introductoryPrice': discountMap,
-  };
-
-  final Map<String, List<dynamic>> productResponseMap = <String, List<dynamic>>{
-    'products': <Map<String, dynamic>>[productMap],
-    'invalidProductIdentifiers': <String>['123'],
-  };
-
-  setUpAll(() =>
-      channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler));
-
-  group('startProductRequest api', () {
-    test('platform call should get result', () async {
-      stubPlatform.addResponse(
-          name: '-[InAppPurchasePlugin startProductRequest:result:]',
-          value: productResponseMap);
-      final SKRequestMaker request = SKRequestMaker();
-      final SkProductResponseWrapper response =
-          await request.startProductRequest(<String>['123']);
-      expect(
-        response.products,
-        isNotEmpty,
-      );
-      expect(
-        response.products.first.priceLocale.currencySymbol,
-        '\$',
-      );
-      expect(
-        response.products.first.priceLocale.currencySymbol,
-        isNot('A'),
-      );
-      expect(
-        response.invalidProductIdentifiers,
-        isNotEmpty,
-      );
-    });
-
-    test('result is null should throw', () async {
-      stubPlatform.addResponse(
-          name: '-[InAppPurchasePlugin startProductRequest:result:]',
-          value: null);
-      final SKRequestMaker request = SKRequestMaker();
-      expect(
-        request.startProductRequest(<String>['123']),
-        throwsException,
-      );
-    });
-  });
-}
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart
new file mode 100644
index 0000000..7fc8363
--- /dev/null
+++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart
@@ -0,0 +1,179 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:in_app_purchase/store_kit_wrappers.dart';
+
+final dummyPayment = SKPaymentWrapper(
+    productIdentifier: 'prod-id',
+    applicationUsername: 'app-user-name',
+    requestData: 'fake-data-utf8',
+    quantity: 2,
+    simulatesAskToBuyInSandbox: true);
+final SKError dummyError =
+    SKError(code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'});
+final SKDownloadWrapper dummyDownload = SKDownloadWrapper(
+  contentIdentifier: 'id',
+  state: SKDownloadState.failed,
+  contentLength: 32,
+  contentURL: 'https://download.com',
+  contentVersion: '0.0.1',
+  transactionID: 'tranID',
+  progress: 0.6,
+  timeRemaining: 1231231,
+  downloadTimeUnknown: false,
+  error: dummyError,
+);
+final SKPaymentTransactionWrapper dummyOriginalTransaction =
+    SKPaymentTransactionWrapper(
+  transactionState: SKPaymentTransactionStateWrapper.purchased,
+  payment: dummyPayment,
+  originalTransaction: null,
+  transactionTimeStamp: 1231231231.00,
+  transactionIdentifier: '123123',
+  downloads: [dummyDownload],
+  error: dummyError,
+);
+final SKPaymentTransactionWrapper dummyTransaction =
+    SKPaymentTransactionWrapper(
+  transactionState: SKPaymentTransactionStateWrapper.purchased,
+  payment: dummyPayment,
+  originalTransaction: dummyOriginalTransaction,
+  transactionTimeStamp: 1231231231.00,
+  transactionIdentifier: '123123',
+  downloads: [dummyDownload],
+  error: dummyError,
+);
+
+final SKPriceLocaleWrapper dummyLocale =
+    SKPriceLocaleWrapper(currencySymbol: '\$');
+
+final SKProductSubscriptionPeriodWrapper dummySubscription =
+    SKProductSubscriptionPeriodWrapper(
+  numberOfUnits: 1,
+  unit: SKSubscriptionPeriodUnit.month,
+);
+
+final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper(
+  price: '1.0',
+  priceLocale: dummyLocale,
+  numberOfPeriods: 1,
+  paymentMode: SKProductDiscountPaymentMode.payUpFront,
+  subscriptionPeriod: dummySubscription,
+);
+
+final SKProductWrapper dummyProductWrapper = SKProductWrapper(
+  productIdentifier: 'id',
+  localizedTitle: 'title',
+  localizedDescription: 'description',
+  priceLocale: dummyLocale,
+  downloadContentVersion: 'version',
+  subscriptionGroupIdentifier: 'com.group',
+  price: '1.0',
+  downloadable: true,
+  downloadContentLengths: <int>[1, 2],
+  subscriptionPeriod: dummySubscription,
+  introductoryPrice: dummyDiscount,
+);
+
+final SkProductResponseWrapper dummyProductResponseWrapper =
+    SkProductResponseWrapper(
+  products: [dummyProductWrapper],
+  invalidProductIdentifiers: <String>['123'],
+);
+
+Map<String, dynamic> buildLocaleMap(SKPriceLocaleWrapper local) {
+  return {'currencySymbol': local.currencySymbol};
+}
+
+Map<String, dynamic> buildSubscriptionPeriodMap(
+    SKProductSubscriptionPeriodWrapper sub) {
+  return {
+    'numberOfUnits': sub.numberOfUnits,
+    'unit': SKSubscriptionPeriodUnit.values.indexOf(sub.unit),
+  };
+}
+
+Map<String, dynamic> buildDiscountMap(SKProductDiscountWrapper discount) {
+  return {
+    'price': discount.price,
+    'priceLocale': buildLocaleMap(discount.priceLocale),
+    'numberOfPeriods': discount.numberOfPeriods,
+    'paymentMode':
+        SKProductDiscountPaymentMode.values.indexOf(discount.paymentMode),
+    'subscriptionPeriod':
+        buildSubscriptionPeriodMap(discount.subscriptionPeriod),
+  };
+}
+
+Map<String, dynamic> buildProductMap(SKProductWrapper product) {
+  return {
+    'productIdentifier': product.productIdentifier,
+    'localizedTitle': product.localizedTitle,
+    'localizedDescription': product.localizedDescription,
+    'priceLocale': buildLocaleMap(product.priceLocale),
+    'downloadContentVersion': product.downloadContentVersion,
+    'subscriptionGroupIdentifier': product.subscriptionGroupIdentifier,
+    'price': product.price,
+    'downloadable': product.downloadable,
+    'downloadContentLengths': product.downloadContentLengths,
+    'subscriptionPeriod':
+        buildSubscriptionPeriodMap(product.subscriptionPeriod),
+    'introductoryPrice': buildDiscountMap(product.introductoryPrice),
+  };
+}
+
+Map<String, dynamic> buildProductResponseMap(
+    SkProductResponseWrapper response) {
+  List productsMap = response.products
+      .map((SKProductWrapper product) => buildProductMap(product))
+      .toList();
+  return {
+    'products': productsMap,
+    'invalidProductIdentifiers': response.invalidProductIdentifiers
+  };
+}
+
+Map<String, dynamic> buildErrorMap(SKError error) {
+  return {
+    'code': error.code,
+    'domain': error.domain,
+    'userInfo': error.userInfo,
+  };
+}
+
+Map<String, dynamic> buildDownloadMap(SKDownloadWrapper download) {
+  return {
+    'contentIdentifier': download.contentIdentifier,
+    'state': SKDownloadState.values.indexOf(download.state),
+    'contentLength': download.contentLength,
+    'contentURL': download.contentURL,
+    'contentVersion': download.contentVersion,
+    'transactionID': download.transactionID,
+    'progress': download.progress,
+    'timeRemaining': download.timeRemaining,
+    'downloadTimeUnknown': download.downloadTimeUnknown,
+    'error': buildErrorMap(download.error)
+  };
+}
+
+Map<String, dynamic> buildTransactionMap(
+    SKPaymentTransactionWrapper transaction) {
+  if (transaction == null) {
+    return null;
+  }
+  Map map = <String, dynamic>{
+    'transactionState': SKPaymentTransactionStateWrapper.values
+        .indexOf(SKPaymentTransactionStateWrapper.purchased),
+    'payment': transaction.payment.toMap(),
+    'originalTransaction': buildTransactionMap(transaction.originalTransaction),
+    'transactionTimeStamp': transaction.transactionTimeStamp,
+    'transactionIdentifier': transaction.transactionIdentifier,
+    'error': buildErrorMap(transaction.error),
+  };
+  List downloadList = transaction.downloads.map((SKDownloadWrapper download) {
+    return buildDownloadMap(download);
+  }).toList();
+  map['downloads'] = downloadList;
+  return map;
+}