[in_app_purchase_storekit] Remove OCMock (#6862)

Fixes https://github.com/flutter/flutter/issues/149849

Also fixes the broken symlinking between test files.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [relevant style guides] and ran the
auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages
repo does use `dart format`.)
- [x] I signed the [CLA].
- [x] The title of the PR starts with the name of the package surrounded
by square brackets, e.g. `[shared_preferences]`
- [x] I [linked to at least one issue that this PR fixes] in the
description above.
- [x] I updated `pubspec.yaml` with an appropriate new version according
to the [pub versioning philosophy], or this PR is [exempt from version
changes].
- [x] I updated `CHANGELOG.md` to add a description of the change,
[following repository CHANGELOG style], or this PR is [exempt from
CHANGELOG changes].
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[relevant style guides]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style
[CLA]: https://cla.developers.google.com/
[Discord]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md
[linked to at least one issue that this PR fixes]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[pub versioning philosophy]: https://dart.dev/tools/pub/versioning
[exempt from version changes]:
https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#version
[following repository CHANGELOG style]:
https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog-style
[exempt from CHANGELOG changes]:
https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md
index 070f075..18072ee 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md
@@ -1,6 +1,10 @@
+## 0.3.17
+
+* Removes OCMock from tests.
+
 ## 0.3.16
 
-* Converts main plugin class to Swift
+* Converts main plugin class to Swift.
 
 ## 0.3.15
 
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h
index 4347846..12ef96b 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h
@@ -9,13 +9,14 @@
 #endif
 #import <Foundation/Foundation.h>
 #import <StoreKit/StoreKit.h>
+#import "FLTMethodChannelProtocol.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
 API_AVAILABLE(ios(13))
 API_UNAVAILABLE(tvos, macos, watchos)
 @interface FIAPPaymentQueueDelegate : NSObject <SKPaymentQueueDelegate>
-- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel;
+- (id)initWithMethodChannel:(id<FLTMethodChannelProtocol>)methodChannel;
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m
index cb18d9b..d0efb06 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m
@@ -7,13 +7,14 @@
 
 @interface FIAPPaymentQueueDelegate ()
 
-@property(strong, nonatomic, readonly) FlutterMethodChannel *callbackChannel;
+// The designated Flutter method channel that handles if a transaction should be continued
+@property(nonatomic, strong, readonly) id<FLTMethodChannelProtocol> callbackChannel;
 
 @end
 
 @implementation FIAPPaymentQueueDelegate
 
-- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel {
+- (id)initWithMethodChannel:(id<FLTMethodChannelProtocol>)methodChannel {
   self = [super init];
   if (self) {
     _callbackChannel = methodChannel;
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m
index 22a3973..503eb0f 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m
@@ -14,13 +14,15 @@
 // Gets the receipt file data from the location of the url. Can be nil if
 // there is an error. This interface is defined so it can be stubbed for testing.
 - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error;
-
+// Gets the app store receipt url. Can be nil if
+// there is an error. This property is defined so it can be stubbed for testing.
+@property(nonatomic, readonly) NSURL *receiptURL;
 @end
 
 @implementation FIAPReceiptManager
 
 - (NSString *)retrieveReceiptWithError:(FlutterError **)flutterError {
-  NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
+  NSURL *receiptURL = self.receiptURL;
   if (!receiptURL) {
     return nil;
   }
@@ -43,4 +45,8 @@
   return [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:error];
 }
 
+- (NSURL *)receiptURL {
+  return [[NSBundle mainBundle] appStoreReceiptURL];
+}
+
 @end
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h
index cbf21d6..ea6e2b7 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h
@@ -2,19 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <FLTRequestHandlerProtocol.h>
 #import <Foundation/Foundation.h>
 #import <StoreKit/StoreKit.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
-typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response,
-                                         NSError *_Nullable errror);
-
-@interface FIAPRequestHandler : NSObject
+@interface FIAPRequestHandler : NSObject <FLTRequestHandlerProtocol>
 
 - (instancetype)initWithRequest:(SKRequest *)request;
 - (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion;
 
 @end
 
+// The default request handler that wraps FIAPRequestHandler
+@interface DefaultRequestHandler : NSObject <FLTRequestHandlerProtocol>
+
+// Initialize this wrapper with an instance of FIAPRequestHandler
+- (instancetype)initWithRequestHandler:(FIAPRequestHandler *)handler;
+@end
 NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m
index 8767265..d2ef682 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m
@@ -9,8 +9,8 @@
 
 @interface FIAPRequestHandler () <SKProductsRequestDelegate>
 
-@property(copy, nonatomic) ProductRequestCompletion completion;
-@property(strong, nonatomic) SKRequest *request;
+@property(nonatomic, copy) ProductRequestCompletion completion;
+@property(nonatomic, strong) SKRequest *request;
 
 @end
 
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h
index 356940c..68ddcaa 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h
@@ -5,130 +5,16 @@
 #import <Foundation/Foundation.h>
 #import <StoreKit/StoreKit.h>
 #import "FIATransactionCache.h"
+#import "FLTPaymentQueueHandlerProtocol.h"
+#import "FLTPaymentQueueProtocol.h"
+#import "FLTTransactionCacheProtocol.h"
 
 @class SKPaymentTransaction;
 
 NS_ASSUME_NONNULL_BEGIN
 
-typedef void (^TransactionsUpdated)(NSArray<SKPaymentTransaction *> *transactions);
-typedef void (^TransactionsRemoved)(NSArray<SKPaymentTransaction *> *transactions);
-typedef void (^RestoreTransactionFailed)(NSError *error);
-typedef void (^RestoreCompletedTransactionsFinished)(void);
-typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product);
-typedef void (^UpdatedDownloads)(NSArray<SKDownload *> *downloads);
-
-@interface FIAPaymentQueueHandler : NSObject <SKPaymentTransactionObserver>
-
-@property(NS_NONATOMIC_IOSONLY, weak, nullable) id<SKPaymentQueueDelegate> delegate API_AVAILABLE(
-    ios(13.0), macos(10.15), watchos(6.2));
-@property(nonatomic, readonly, nullable)
-    SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2));
-
-/// Creates a new FIAPaymentQueueHandler initialized with an empty
-/// FIATransactionCache.
-///
-/// @param queue The SKPaymentQueue instance connected to the App Store and
-///              responsible for processing transactions.
-/// @param transactionsUpdated Callback method that is called each time the App
-///                            Store indicates transactions are updated.
-/// @param transactionsRemoved Callback method that is called each time the App
-///                            Store indicates transactions are removed.
-/// @param restoreTransactionFailed Callback method that is called each time
-///                                 the App Store indicates transactions failed
-///                                 to restore.
-/// @param restoreCompletedTransactionsFinished Callback method that is called
-///                                             each time the App Store
-///                                             indicates restoring of
-///                                             transactions has finished.
-/// @param shouldAddStorePayment Callback method that is called each time an
-///                              in-app purchase has been initiated from the
-///                              App Store.
-/// @param updatedDownloads Callback method that is called each time the App
-///                         Store indicates downloads are updated.
-- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
-                     transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
-                      transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
-                restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
-    restoreCompletedTransactionsFinished:
-        (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished
-                   shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment
-                        updatedDownloads:(nullable UpdatedDownloads)updatedDownloads
-    DEPRECATED_MSG_ATTRIBUTE(
-        "Use the "
-        "'initWithQueue:transactionsUpdated:transactionsRemoved:restoreTransactionsFinished:"
-        "shouldAddStorePayment:updatedDownloads:transactionCache:' message instead.");
-
-/// Creates a new FIAPaymentQueueHandler.
-///
-/// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads"
-/// callbacks are only called while actively observing transactions. To start
-/// observing transactions send the "startObservingPaymentQueue" message.
-/// Sending the "stopObservingPaymentQueue" message will stop actively
-/// observing transactions. When transactions are not observed they are cached
-/// to the "transactionCache" and will be delivered via the
-/// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads"
-/// callbacks as soon as the "startObservingPaymentQueue" message arrives.
-///
-/// Note: cached transactions that are not processed when the application is
-/// killed will be delivered again by the App Store as soon as the application
-/// starts again.
-///
-/// @param queue The SKPaymentQueue instance connected to the App Store and
-///              responsible for processing transactions.
-/// @param transactionsUpdated Callback method that is called each time the App
-///                            Store indicates transactions are updated.
-/// @param transactionsRemoved Callback method that is called each time the App
-///                            Store indicates transactions are removed.
-/// @param restoreTransactionFailed Callback method that is called each time
-///                                 the App Store indicates transactions failed
-///                                 to restore.
-/// @param restoreCompletedTransactionsFinished Callback method that is called
-///                                             each time the App Store
-///                                             indicates restoring of
-///                                             transactions has finished.
-/// @param shouldAddStorePayment Callback method that is called each time an
-///                              in-app purchase has been initiated from the
-///                              App Store.
-/// @param updatedDownloads Callback method that is called each time the App
-///                         Store indicates downloads are updated.
-/// @param transactionCache An empty [FIATransactionCache] instance that is
-///                         responsible for keeping track of transactions that
-///                         arrive when not actively observing transactions.
-- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
-                     transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
-                      transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
-                restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
-    restoreCompletedTransactionsFinished:
-        (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished
-                   shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment
-                        updatedDownloads:(nullable UpdatedDownloads)updatedDownloads
-                        transactionCache:(nonnull FIATransactionCache *)transactionCache;
-// Can throw exceptions if the transaction type is purchasing, should always used in a @try block.
-- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction;
-- (void)restoreTransactions:(nullable NSString *)applicationName;
-- (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos);
-- (NSArray<SKPaymentTransaction *> *)getUnfinishedTransactions;
-
-// This method needs to be called before any other methods.
-- (void)startObservingPaymentQueue;
-// Call this method when the Flutter app is no longer listening
-- (void)stopObservingPaymentQueue;
-
-// Appends a payment to the SKPaymentQueue.
-//
-// @param payment Payment object to be added to the payment queue.
-// @return whether "addPayment" was successful.
-- (BOOL)addPayment:(SKPayment *)payment;
-
-// Displays the price consent sheet.
-//
-// The price consent sheet is only displayed when the following
-// is true:
-// - You have increased the price of the subscription in App Store Connect.
-// - The subscriber has not yet responded to a price consent query.
-// Otherwise the method has no effect.
-- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos);
-
+@interface FIAPaymentQueueHandler
+    : NSObject <SKPaymentTransactionObserver, FLTPaymentQueueHandlerProtocol>
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m
index 6005657..e221ce0 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m
@@ -10,34 +10,34 @@
 
 /// The SKPaymentQueue instance connected to the App Store and responsible for processing
 /// transactions.
-@property(strong, nonatomic) SKPaymentQueue *queue;
+@property(nonatomic, strong) SKPaymentQueue *queue;
 
 /// Callback method that is called each time the App Store indicates transactions are updated.
-@property(nullable, copy, nonatomic) TransactionsUpdated transactionsUpdated;
+@property(nonatomic, nullable, copy) TransactionsUpdated transactionsUpdated;
 
 /// Callback method that is called each time the App Store indicates transactions are removed.
-@property(nullable, copy, nonatomic) TransactionsRemoved transactionsRemoved;
+@property(nonatomic, nullable, copy) TransactionsRemoved transactionsRemoved;
 
 /// Callback method that is called each time the App Store indicates transactions failed to restore.
-@property(nullable, copy, nonatomic) RestoreTransactionFailed restoreTransactionFailed;
+@property(nonatomic, nullable, copy) RestoreTransactionFailed restoreTransactionFailed;
 
 /// Callback method that is called each time the App Store indicates restoring of transactions has
 /// finished.
-@property(nullable, copy, nonatomic)
+@property(nonatomic, nullable, copy)
     RestoreCompletedTransactionsFinished paymentQueueRestoreCompletedTransactionsFinished;
 
 /// Callback method that is called each time an in-app purchase has been initiated from the App
 /// Store.
-@property(nullable, copy, nonatomic) ShouldAddStorePayment shouldAddStorePayment;
+@property(nonatomic, nullable, copy) ShouldAddStorePayment shouldAddStorePayment;
 
 /// Callback method that is called each time the App Store indicates downloads are updated.
-@property(nullable, copy, nonatomic) UpdatedDownloads updatedDownloads;
+@property(nonatomic, nullable, copy) UpdatedDownloads updatedDownloads;
 
 /// The transaction cache responsible for caching transactions.
 ///
 /// Keeps track of transactions that arrive when the Flutter client is not
 /// actively observing for transactions.
-@property(strong, nonatomic, nonnull) FIATransactionCache *transactionCache;
+@property(nonatomic, strong, nonnull) FIATransactionCache *transactionCache;
 
 /// Indicates if the Flutter client is observing transactions.
 ///
@@ -51,7 +51,9 @@
 
 @implementation FIAPaymentQueueHandler
 
-- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
+@synthesize delegate;
+
+- (instancetype)initWithQueue:(nonnull id<FLTPaymentQueueProtocol>)queue
                      transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
                       transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
                 restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
@@ -66,10 +68,10 @@
                   restoreCompletedTransactionsFinished:restoreCompletedTransactionsFinished
                                  shouldAddStorePayment:shouldAddStorePayment
                                       updatedDownloads:updatedDownloads
-                                      transactionCache:[[FIATransactionCache alloc] init]];
+                                      transactionCache:[[DefaultTransactionCache alloc] init]];
 }
 
-- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
+- (instancetype)initWithQueue:(nonnull id<FLTPaymentQueueProtocol>)queue
                      transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
                       transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
                 restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
@@ -77,7 +79,7 @@
         (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished
                    shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment
                         updatedDownloads:(nullable UpdatedDownloads)updatedDownloads
-                        transactionCache:(nonnull FIATransactionCache *)transactionCache {
+                        transactionCache:(nonnull id<FLTTransactionCacheProtocol>)transactionCache {
   self = [super init];
   if (self) {
     _queue = queue;
@@ -170,7 +172,7 @@
 #endif
 
 #if TARGET_OS_IOS
-- (void)showPriceConsentIfNeeded {
+- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4)) {
   [self.queue showPriceConsentIfNeeded];
 }
 #endif
@@ -233,7 +235,7 @@
   return self.queue.transactions;
 }
 
-- (SKStorefront *)storefront {
+- (SKStorefront *)storefront API_AVAILABLE(ios(13.0)) {
   return self.queue.storefront;
 }
 
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift
index 511fc26..4ebd882 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift
@@ -18,17 +18,19 @@
   // note - the type should be FIAPPaymentQueueDelegate, but this is only available >= iOS 13,
   // FIAPPaymentQueueDelegate only gets set/used in registerPaymentQueueDelegateWithError or removePaymentQueueDelegateWithError, which both are ios13+ only
   private var paymentQueueDelegate: Any?
-  private var requestHandlers = Set<FIAPRequestHandler>()
-  private var handlerFactory: ((SKRequest) -> FIAPRequestHandler)
+  // Swift sets do not accept protocols, only concrete implementations
+  // TODO(louisehsu): Change it back to a set when removing obj-c dependancies from this file via type erasure
+  private var requestHandlers = NSHashTable<FLTRequestHandlerProtocol>()
+  private var handlerFactory: ((SKRequest) -> FLTRequestHandlerProtocol)
   // TODO(louisehsu): Once tests are migrated to swift, we can use @testable import, and make theses vars private again and remove all instances of @objc
   @objc
   public var registrar: FlutterPluginRegistrar?
   // This property is optional, as it requires self to exist to be initialized.
   @objc
-  public var paymentQueueHandler: FIAPaymentQueueHandler?
+  public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol?
   // This property is optional, as it needs to be set during plugin registration, and can't be directly initialized.
   @objc
-  public var transactionObserverCallbackChannel: FlutterMethodChannel?
+  public var transactionObserverCallbackChannel: FLTMethodChannelProtocol?
 
   public static func register(with registrar: FlutterPluginRegistrar) {
     #if os(iOS)
@@ -50,8 +52,8 @@
   // This init is used for tests
   public init(
     receiptManager: FIAPReceiptManager,
-    handlerFactory: @escaping (SKRequest) -> FIAPRequestHandler = {
-      FIAPRequestHandler(request: $0)
+    handlerFactory: @escaping (SKRequest) -> FLTRequestHandlerProtocol = {
+      DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: $0))
     }
   ) {
     self.receiptManager = receiptManager
@@ -65,7 +67,7 @@
     self.registrar = registrar
 
     self.paymentQueueHandler = FIAPaymentQueueHandler(
-      queue: SKPaymentQueue.default(),
+      queue: DefaultPaymentQueue(queue: SKPaymentQueue.default()),
       transactionsUpdated: { [weak self] transactions in
         self?.handleTransactionsUpdated(transactions)
       },
@@ -84,15 +86,18 @@
       updatedDownloads: { [weak self] _ in
         self?.updatedDownloads()
       },
-      transactionCache: FIATransactionCache())
+      transactionCache: DefaultTransactionCache(cache: FIATransactionCache()))
     #if os(iOS)
       let messenger = registrar.messenger()
     #endif
     #if os(macOS)
       let messenger = registrar.messenger
     #endif
-    transactionObserverCallbackChannel = FlutterMethodChannel(
-      name: "plugins.flutter.io/in_app_purchase", binaryMessenger: messenger)
+    transactionObserverCallbackChannel = DefaultMethodChannel(
+      channel: FlutterMethodChannel(
+        name: "plugins.flutter.io/in_app_purchase",
+        binaryMessenger: messenger)
+    )
   }
 
   // MARK: - Pigeon Functions
@@ -128,7 +133,7 @@
   ) {
     let request = getProductRequest(withIdentifiers: Set(productIdentifiers))
     let handler = handlerFactory(request)
-    requestHandlers.insert(handler)
+    requestHandlers.add(handler)
 
     handler.startProductRequest { [weak self] response, startProductRequestError in
       guard let self = self else { return }
@@ -282,7 +287,7 @@
     let properties = receiptProperties?.compactMapValues { $0 } ?? [:]
     let request = getRefreshReceiptRequest(properties: properties.isEmpty ? nil : properties)
     let handler = handlerFactory(request)
-    requestHandlers.insert(handler)
+    requestHandlers.add(handler)
     handler.startProductRequest { [weak self] response, error in
       if let error = error {
         let requestError = FlutterError(
@@ -322,10 +327,10 @@
           binaryMessenger: messenger)
 
         guard let unwrappedChannel = paymentQueueDelegateCallbackChannel else {
-          fatalError("registrar.messenger can not be nil.")
+          fatalError("paymentQueueDelegateCallbackChannel can not be nil.")
         }
         paymentQueueDelegate = FIAPPaymentQueueDelegate(
-          methodChannel: unwrappedChannel)
+          methodChannel: DefaultMethodChannel(channel: unwrappedChannel))
 
         getPaymentQueueHandler().delegate = paymentQueueDelegate as? SKPaymentQueueDelegate
       }
@@ -419,7 +424,7 @@
     return value is NSNull ? nil : value
   }
 
-  private func getPaymentQueueHandler() -> FIAPaymentQueueHandler {
+  private func getPaymentQueueHandler() -> FLTPaymentQueueHandlerProtocol {
     guard let paymentQueueHandler = self.paymentQueueHandler else {
       fatalError(
         "paymentQueueHandler can't be nil. Please ensure you're using init(registrar: FlutterPluginRegistrar)"
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h
new file mode 100644
index 0000000..1285643
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h
@@ -0,0 +1,33 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if TARGET_OS_OSX
+#import <FlutterMacOS/FlutterMacOS.h>
+#else
+#import <Flutter/Flutter.h>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+/// A protocol that wraps FlutterMethodChannel.
+@protocol FLTMethodChannelProtocol <NSObject>
+
+/// Invokes the specified Flutter method with the specified arguments, expecting
+/// an asynchronous result.
+- (void)invokeMethod:(NSString *)method arguments:(id _Nullable)arguments;
+
+/// Invokes the specified Flutter method with the specified arguments and specified callback
+- (void)invokeMethod:(NSString *)method
+           arguments:(id _Nullable)arguments
+              result:(FlutterResult _Nullable)callback;
+
+@end
+
+/// The default method channel that wraps FlutterMethodChannel
+@interface DefaultMethodChannel : NSObject <FLTMethodChannelProtocol>
+
+/// Initialize this wrapper with a FlutterMethodChannel
+- (instancetype)initWithChannel:(FlutterMethodChannel *)channel;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m
new file mode 100644
index 0000000..17e0e08
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m
@@ -0,0 +1,32 @@
+// Copyright 2013 The Flutter 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 "FLTMethodChannelProtocol.h"
+
+@interface DefaultMethodChannel ()
+/// The wrapped FlutterMethodChannel
+@property(nonatomic, strong) FlutterMethodChannel *channel;
+@end
+
+@implementation DefaultMethodChannel
+
+- (instancetype)initWithChannel:(nonnull FlutterMethodChannel *)channel {
+  self = [super init];
+  if (self) {
+    _channel = channel;
+  }
+  return self;
+}
+
+- (void)invokeMethod:(nonnull NSString *)method arguments:(id _Nullable)arguments {
+  [self.channel invokeMethod:method arguments:arguments];
+}
+
+- (void)invokeMethod:(nonnull NSString *)method
+           arguments:(id _Nullable)arguments
+              result:(FlutterResult _Nullable)callback {
+  [self.channel invokeMethod:method arguments:arguments result:callback];
+}
+
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h
new file mode 100644
index 0000000..f11b1a0
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h
@@ -0,0 +1,107 @@
+// Copyright 2013 The Flutter 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 <StoreKit/StoreKit.h>
+#import "FIATransactionCache.h"
+#import "FLTPaymentQueueProtocol.h"
+#import "FLTTransactionCacheProtocol.h"
+
+NS_ASSUME_NONNULL_BEGIN
+typedef void (^TransactionsUpdated)(NSArray<SKPaymentTransaction *> *transactions);
+typedef void (^TransactionsRemoved)(NSArray<SKPaymentTransaction *> *transactions);
+typedef void (^RestoreTransactionFailed)(NSError *error);
+typedef void (^RestoreCompletedTransactionsFinished)(void);
+typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product);
+typedef void (^UpdatedDownloads)(NSArray<SKDownload *> *downloads);
+
+/// A protocol that conforms to SKPaymentTransactionObserver and handles SKPaymentQueue methods
+@protocol FLTPaymentQueueHandlerProtocol <NSObject, SKPaymentTransactionObserver>
+/// An object that provides information needed to complete transactions.
+@property(nonatomic, weak, nullable) id<SKPaymentQueueDelegate> delegate API_AVAILABLE(
+    ios(13.0), macos(10.15), watchos(6.2));
+/// An object containing the location and unique identifier of an Apple App Store storefront.
+@property(nonatomic, readonly, nullable)
+    SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2));
+
+/// Creates a new FIAPaymentQueueHandler.
+///
+/// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads"
+/// callbacks are only called while actively observing transactions. To start
+/// observing transactions send the "startObservingPaymentQueue" message.
+/// Sending the "stopObservingPaymentQueue" message will stop actively
+/// observing transactions. When transactions are not observed they are cached
+/// to the "transactionCache" and will be delivered via the
+/// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads"
+/// callbacks as soon as the "startObservingPaymentQueue" message arrives.
+///
+/// Note: cached transactions that are not processed when the application is
+/// killed will be delivered again by the App Store as soon as the application
+/// starts again.
+///
+/// @param queue The SKPaymentQueue instance connected to the App Store and
+///              responsible for processing transactions.
+/// @param transactionsUpdated Callback method that is called each time the App
+///                            Store indicates transactions are updated.
+/// @param transactionsRemoved Callback method that is called each time the App
+///                            Store indicates transactions are removed.
+/// @param restoreTransactionFailed Callback method that is called each time
+///                                 the App Store indicates transactions failed
+///                                 to restore.
+/// @param restoreCompletedTransactionsFinished Callback method that is called
+///                                             each time the App Store
+///                                             indicates restoring of
+///                                             transactions has finished.
+/// @param shouldAddStorePayment Callback method that is called each time an
+///                              in-app purchase has been initiated from the
+///                              App Store.
+/// @param updatedDownloads Callback method that is called each time the App
+///                         Store indicates downloads are updated.
+/// @param transactionCache An empty [FIATransactionCache] instance that is
+///                         responsible for keeping track of transactions that
+///                         arrive when not actively observing transactions.
+- (instancetype)initWithQueue:(id<FLTPaymentQueueProtocol>)queue
+                     transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
+                      transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
+                restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
+    restoreCompletedTransactionsFinished:
+        (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished
+                   shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment
+                        updatedDownloads:(nullable UpdatedDownloads)updatedDownloads
+                        transactionCache:(nonnull id<FLTTransactionCacheProtocol>)transactionCache;
+
+/// Can throw exceptions if the transaction type is purchasing, should always used in a @try block.
+- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction;
+
+/// Attempt to restore transactions. Require app store receipt url.
+- (void)restoreTransactions:(nullable NSString *)applicationName;
+
+/// Displays a sheet that enables users to redeem subscription offer codes.
+- (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos);
+
+/// Return all transactions that are not marked as complete.
+- (NSArray<SKPaymentTransaction *> *)getUnfinishedTransactions;
+
+/// This method needs to be called before any other methods.
+- (void)startObservingPaymentQueue;
+
+/// Call this method when the Flutter app is no longer listening
+- (void)stopObservingPaymentQueue;
+
+/// Appends a payment to the SKPaymentQueue.
+///
+/// @param payment Payment object to be added to the payment queue.
+/// @return whether "addPayment" was successful.
+- (BOOL)addPayment:(SKPayment *)payment;
+
+/// Displays the price consent sheet.
+///
+/// The price consent sheet is only displayed when the following
+/// is true:
+/// - You have increased the price of the subscription in App Store Connect.
+/// - The subscriber has not yet responded to a price consent query.
+/// Otherwise the method has no effect.
+- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos);
+
+@end
+NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h
new file mode 100644
index 0000000..4047647
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h
@@ -0,0 +1,69 @@
+// Copyright 2013 The Flutter 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 <StoreKit/StoreKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A protocol that wraps SKPaymentQueue
+@protocol FLTPaymentQueueProtocol <NSObject>
+
+/// An object containing the location and unique identifier of an Apple App Store storefront.
+@property(nonatomic, strong) SKStorefront *storefront API_AVAILABLE(ios(13.0));
+
+/// A list of SKPaymentTransactions, which each represents a single transaction
+@property(nonatomic, strong) NSArray<SKPaymentTransaction *> *transactions API_AVAILABLE(
+    ios(3.0), macos(10.7), watchos(6.2), visionos(1.0));
+
+/// An object that provides information needed to complete transactions.
+@property(nonatomic, weak, nullable) id<SKPaymentQueueDelegate> delegate API_AVAILABLE(
+    ios(13.0), macos(10.15), watchos(6.2), visionos(1.0));
+
+/// Remove a finished (i.e. failed or completed) transaction from the queue.  Attempting to finish a
+/// purchasing transaction will throw an exception.
+- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction;
+
+/// Observers are not retained.  The transactions array will only be synchronized with the server
+/// while the queue has observers.  This may require that the user authenticate.
+- (void)addTransactionObserver:(id<SKPaymentTransactionObserver>)observer;
+
+/// Add a payment to the server queue.  The payment is copied to add an SKPaymentTransaction to the
+/// transactions array.  The same payment can be added multiple times to create multiple
+/// transactions.
+- (void)addPayment:(SKPayment *_Nonnull)payment;
+
+/// Will add completed transactions for the current user back to the queue to be re-completed.
+- (void)restoreCompletedTransactions API_AVAILABLE(ios(3.0), macos(10.7), watchos(6.2),
+                                                   visionos(1.0));
+
+/// Will add completed transactions for the current user back to the queue to be re-completed. This
+/// version requires an identifier to the user's account.
+- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username
+    API_AVAILABLE(ios(7.0), macos(10.9), watchos(6.2), visionos(1.0));
+
+/// Call this method to have StoreKit present a sheet enabling the user to redeem codes provided by
+/// your app. Only for iOS.
+- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0), visionos(1.0))
+    API_UNAVAILABLE(tvos, macos, watchos);
+
+/// If StoreKit has called your SKPaymentQueueDelegate's "paymentQueueShouldShowPriceConsent:"
+/// method and you returned NO, you can use this method to show the price consent UI at a later time
+/// that is more appropriate for your app. If there is no pending price consent, this method will do
+/// nothing.
+- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4), visionos(1.0))
+    API_UNAVAILABLE(tvos, macos, watchos);
+
+@end
+
+/// The default PaymentQueue that wraps SKPaymentQueue
+@interface DefaultPaymentQueue : NSObject <FLTPaymentQueueProtocol>
+
+/// Initialize this wrapper with an SKPaymentQueue
+- (instancetype)initWithQueue:(SKPaymentQueue *)queue NS_DESIGNATED_INITIALIZER;
+
+/// The default initializer is unavailable, as it this must be initlai
+- (instancetype)init NS_UNAVAILABLE;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m
new file mode 100644
index 0000000..fd97794
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m
@@ -0,0 +1,74 @@
+// Copyright 2013 The Flutter 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 "FLTPaymentQueueProtocol.h"
+
+@interface DefaultPaymentQueue ()
+/// The wrapped SKPaymentQueue
+@property(nonatomic, strong) SKPaymentQueue *queue;
+@end
+
+@implementation DefaultPaymentQueue
+
+@synthesize storefront;
+@synthesize delegate;
+@synthesize transactions;
+
+- (instancetype)initWithQueue:(SKPaymentQueue *)queue {
+  self = [super init];
+  if (self) {
+    _queue = queue;
+  }
+  return self;
+}
+
+- (void)addPayment:(SKPayment *_Nonnull)payment {
+  [self.queue addPayment:payment];
+}
+
+- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction {
+  [self.queue finishTransaction:transaction];
+}
+
+- (void)addTransactionObserver:(nonnull id<SKPaymentTransactionObserver>)observer {
+  [self.queue addTransactionObserver:observer];
+}
+
+- (void)restoreCompletedTransactions {
+  [self.queue restoreCompletedTransactions];
+}
+
+- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username {
+  [self.queue restoreCompletedTransactionsWithApplicationUsername:username];
+}
+
+- (id<SKPaymentQueueDelegate>)delegate API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2),
+                                                     visionos(1.0)) {
+  return self.queue.delegate;
+}
+
+- (NSArray<SKPaymentTransaction *> *)transactions API_AVAILABLE(ios(3.0), macos(10.7), watchos(6.2),
+                                                                visionos(1.0)) {
+  return self.queue.transactions;
+}
+
+- (SKStorefront *)storefront API_AVAILABLE(ios(13.0)) {
+  return self.queue.storefront;
+}
+
+#if TARGET_OS_IOS
+- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0), visionos(1.0))
+    API_UNAVAILABLE(tvos, macos, watchos) {
+  [self.queue presentCodeRedemptionSheet];
+}
+#endif
+
+#if TARGET_OS_IOS
+- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4), visionos(1.0))
+    API_UNAVAILABLE(tvos, macos, watchos) {
+  [self.queue showPriceConsentIfNeeded];
+}
+#endif
+
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h
new file mode 100644
index 0000000..d2359d4
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter 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 <StoreKit/StoreKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response,
+                                         NSError *_Nullable errror);
+/// A protocol that wraps SKRequest.
+@protocol FLTRequestHandlerProtocol <NSObject>
+
+/// Wrapper for SKRequest's start
+/// https://developer.apple.com/documentation/storekit/skrequest/1385534-start
+- (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion;
+@end
+NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m
new file mode 100644
index 0000000..e171467
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m
@@ -0,0 +1,27 @@
+// Copyright 2013 The Flutter 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 "FLTRequestHandlerProtocol.h"
+#import <Foundation/Foundation.h>
+#import "FIAPRequestHandler.h"
+
+@interface DefaultRequestHandler ()
+/// The wrapped FIAPRequestHandler
+@property(nonatomic, strong) FIAPRequestHandler *handler;
+@end
+
+@implementation DefaultRequestHandler
+
+- (void)startProductRequestWithCompletionHandler:(nonnull ProductRequestCompletion)completion {
+  [self.handler startProductRequestWithCompletionHandler:completion];
+}
+
+- (nonnull instancetype)initWithRequestHandler:(nonnull FIAPRequestHandler *)handler {
+  self = [super init];
+  if (self) {
+    _handler = handler;
+  }
+  return self;
+}
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h
new file mode 100644
index 0000000..f7a58b3
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h
@@ -0,0 +1,39 @@
+// Copyright 2013 The Flutter 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 "FIATransactionCache.h"
+#if TARGET_OS_OSX
+#import <FlutterMacOS/FlutterMacOS.h>
+#else
+#import <Flutter/Flutter.h>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A protocol that defines a cache of all transactions, both completed and in progress.
+@protocol FLTTransactionCacheProtocol <NSObject>
+
+/// Adds objects to the transaction cache.
+///
+/// If the cache already contains an array of objects on the specified key, the supplied
+/// array will be appended to the existing array.
+- (void)addObjects:(NSArray *)objects forKey:(TransactionCacheKey)key;
+
+/// Gets the array of objects stored at the given key.
+///
+/// If there are no objects associated with the given key nil is returned.
+- (NSArray *)getObjectsForKey:(TransactionCacheKey)key;
+
+/// Removes all objects from the transaction cache.
+- (void)clear;
+@end
+
+/// The default method channel that wraps FIATransactionCache
+@interface DefaultTransactionCache : NSObject <FLTTransactionCacheProtocol>
+
+/// Initialize this wrapper with an FIATransactionCache
+- (instancetype)initWithCache:(FIATransactionCache *)cache;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m
new file mode 100644
index 0000000..3ed268e
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m
@@ -0,0 +1,33 @@
+// Copyright 2013 The Flutter 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 "FLTTransactionCacheProtocol.h"
+
+@interface DefaultTransactionCache ()
+/// The wrapped FIATransactionCache
+@property(nonatomic, strong) FIATransactionCache *cache;
+@end
+
+@implementation DefaultTransactionCache
+
+- (void)addObjects:(nonnull NSArray *)objects forKey:(TransactionCacheKey)key {
+  [self.cache addObjects:objects forKey:key];
+}
+
+- (void)clear {
+  [self.cache clear];
+}
+
+- (nonnull NSArray *)getObjectsForKey:(TransactionCacheKey)key {
+  return [self.cache getObjectsForKey:key];
+}
+
+- (nonnull instancetype)initWithCache:(nonnull FIATransactionCache *)cache {
+  self = [super init];
+  if (self) {
+    _cache = cache;
+  }
+  return self;
+}
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h
index f57780d..e44e5cf 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h
@@ -8,4 +8,9 @@
 #import "FIAPRequestHandler.h"
 #import "FIAPaymentQueueHandler.h"
 #import "FIATransactionCache.h"
+#import "FLTMethodChannelProtocol.h"
+#import "FLTPaymentQueueHandlerProtocol.h"
+#import "FLTPaymentQueueProtocol.h"
+#import "FLTRequestHandlerProtocol.h"
+#import "FLTTransactionCacheProtocol.h"
 #import "messages.g.h"
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile
index 0358424..bb776ac 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile
@@ -33,8 +33,6 @@
   target 'RunnerTests' do
     inherit! :search_paths
 
-    # Matches in_app_purchase test_spec dependency.
-    pod 'OCMock', '~> 3.6'
   end
 end
 
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj
index 5b7ba61..7d99ab6 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj
@@ -7,24 +7,24 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1630769A874F9381BC761FE1 /* libPods-Runner.a */; };
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
-		688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 688DE35021F2A5A100EA2684 /* TranslatorTests.m */; };
-		6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */; };
-		6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34B21EEB4B800D37AEF /* Stubs.m */; };
-		7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
 		A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; };
-		A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */; };
+		C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */; };
+		E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */; };
 		F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */; };
-		F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */; };
-		F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */; };
-		F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3132342BC89008449C7 /* PaymentQueueTests.m */; };
+		F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD392C1256DD0067C78A /* Stubs.m */; };
+		F295AD412C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */; };
+		F295AD422C1256F50067C78A /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */; };
+		F295AD432C1256F50067C78A /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */; };
+		F295AD442C1256F50067C78A /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */; };
+		F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */; };
+		F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD402C1256F50067C78A /* TranslatorTests.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -51,20 +51,16 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
-		1630769A874F9381BC761FE1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-		18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-		2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+		21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		688DE35021F2A5A100EA2684 /* TranslatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TranslatorTests.m; sourceTree = "<group>"; };
-		6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProductRequestHandlerTests.m; sourceTree = "<group>"; };
-		6896B34A21EEB4B800D37AEF /* Stubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stubs.h; sourceTree = "<group>"; };
-		6896B34B21EEB4B800D37AEF /* Stubs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Stubs.m; sourceTree = "<group>"; };
+		6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
 		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -73,18 +69,21 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		A5279297219369C600FF69E6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
 		A59001A421E69658004A3E5E /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-		A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = "<group>"; };
-		A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 		F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = "<group>"; };
 		F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStubs.swift; sourceTree = "<group>"; };
-		F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = "<group>"; };
-		F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; sourceTree = "<group>"; };
+		F295AD362C1251300067C78A /* Stubs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Stubs.h; path = ../../shared/RunnerTests/Stubs.h; sourceTree = "<group>"; };
+		F295AD392C1256DD0067C78A /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = "<group>"; };
+		F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIAPPaymentQueueDeleteTests.m; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.m; sourceTree = "<group>"; };
+		F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = InAppPurchasePluginTests.m; path = ../../shared/RunnerTests/InAppPurchasePluginTests.m; sourceTree = "<group>"; };
+		F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProductRequestHandlerTests.m; path = ../../shared/RunnerTests/ProductRequestHandlerTests.m; sourceTree = "<group>"; };
+		F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIATransactionCacheTests.m; path = ../../shared/RunnerTests/FIATransactionCacheTests.m; sourceTree = "<group>"; };
+		F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PaymentQueueTests.m; path = ../../shared/RunnerTests/PaymentQueueTests.m; sourceTree = "<group>"; };
+		F295AD402C1256F50067C78A /* TranslatorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TranslatorTests.m; path = ../../shared/RunnerTests/TranslatorTests.m; sourceTree = "<group>"; };
 		F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; };
-		F78AF3132342BC89008449C7 /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaymentQueueTests.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -93,7 +92,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */,
-				0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */,
+				E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -101,7 +100,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */,
+				C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -111,10 +110,10 @@
 		0B4403AC68C3196AECF5EF89 /* Pods */ = {
 			isa = PBXGroup;
 			children = (
-				E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */,
-				2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */,
-				9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */,
-				10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */,
+				6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */,
+				CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */,
+				7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */,
+				8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
@@ -187,15 +186,14 @@
 		A59001A521E69658004A3E5E /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
-				A59001A821E69658004A3E5E /* Info.plist */,
-				6896B34A21EEB4B800D37AEF /* Stubs.h */,
-				6896B34B21EEB4B800D37AEF /* Stubs.m */,
-				A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */,
-				6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */,
-				F78AF3132342BC89008449C7 /* PaymentQueueTests.m */,
-				688DE35021F2A5A100EA2684 /* TranslatorTests.m */,
-				F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */,
-				F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */,
+				F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */,
+				F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */,
+				F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */,
+				F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */,
+				F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */,
+				F295AD402C1256F50067C78A /* TranslatorTests.m */,
+				F295AD392C1256DD0067C78A /* Stubs.m */,
+				F295AD362C1251300067C78A /* Stubs.h */,
 				F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */,
 				F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */,
 			);
@@ -206,8 +204,8 @@
 			isa = PBXGroup;
 			children = (
 				A5279297219369C600FF69E6 /* StoreKit.framework */,
-				1630769A874F9381BC761FE1 /* libPods-Runner.a */,
-				18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */,
+				21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */,
+				AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -219,14 +217,14 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
-				EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */,
+				9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
 				97C146EC1CF9000F007C117D /* Resources */,
 				9705A1C41CF9048500538489 /* Embed Frameworks */,
 				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
-				67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */,
+				325E900B3895C722B0E09318 /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -241,7 +239,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = A59001AD21E69658004A3E5E /* Build configuration list for PBXNativeTarget "RunnerTests" */;
 			buildPhases = (
-				95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */,
+				39A4BCA317070A14A6C5C70F /* [CP] Check Pods Manifest.lock */,
 				A59001A021E69658004A3E5E /* Sources */,
 				A59001A121E69658004A3E5E /* Frameworks */,
 				A59001A221E69658004A3E5E /* Resources */,
@@ -323,23 +321,7 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
-			isa = PBXShellScriptBuildPhase;
-			alwaysOutOfDate = 1;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-				"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
-			);
-			name = "Thin Binary";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
-		};
-		67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */ = {
+		325E900B3895C722B0E09318 /* [CP] Copy Pods Resources */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -347,17 +329,19 @@
 			inputPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
 				"${PODS_CONFIGURATION_BUILD_DIR}/in_app_purchase_storekit/in_app_purchase_storekit_privacy.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/shared_preferences_foundation/shared_preferences_foundation_privacy.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
 				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/in_app_purchase_storekit_privacy.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/shared_preferences_foundation_privacy.bundle",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
-		95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */ = {
+		39A4BCA317070A14A6C5C70F /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -379,6 +363,22 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			alwaysOutOfDate = 1;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
+		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			alwaysOutOfDate = 1;
@@ -394,7 +394,7 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
 		};
-		EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */ = {
+		9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -433,14 +433,14 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				F295AD432C1256F50067C78A /* ProductRequestHandlerTests.m in Sources */,
 				F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */,
-				F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */,
-				F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */,
-				6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */,
-				688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */,
-				F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */,
-				A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */,
-				6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */,
+				F295AD412C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m in Sources */,
+				F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */,
+				F295AD442C1256F50067C78A /* FIATransactionCacheTests.m in Sources */,
+				F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */,
+				F295AD422C1256F50067C78A /* InAppPurchasePluginTests.m in Sources */,
+				F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -636,7 +636,7 @@
 		};
 		A59001AB21E69658004A3E5E /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */;
+			baseConfigurationReference = 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -667,7 +667,7 @@
 		};
 		A59001AC21E69658004A3E5E /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */;
+			baseConfigurationReference = 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit
index 29ebbeb..58f3d43 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit
@@ -1,4 +1,14 @@
 {
+  "appPolicies" : {
+    "eula" : "",
+    "policies" : [
+      {
+        "locale" : "en_US",
+        "policyText" : "",
+        "policyURL" : ""
+      }
+    ]
+  },
   "identifier" : "6073E9A3",
   "nonRenewingSubscriptions" : [
 
@@ -118,7 +128,10 @@
           "recurringSubscriptionPeriod" : "P1W",
           "referenceName" : "subscription_silver",
           "subscriptionGroupID" : "D0FEE8D8",
-          "type" : "RecurringSubscription"
+          "type" : "RecurringSubscription",
+          "winbackOffers" : [
+
+          ]
         },
         {
           "adHocOffers" : [
@@ -143,13 +156,16 @@
           "recurringSubscriptionPeriod" : "P1M",
           "referenceName" : "subscription_gold",
           "subscriptionGroupID" : "D0FEE8D8",
-          "type" : "RecurringSubscription"
+          "type" : "RecurringSubscription",
+          "winbackOffers" : [
+
+          ]
         }
       ]
     }
   ],
   "version" : {
-    "major" : 3,
+    "major" : 4,
     "minor" : 0
   }
 }
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile
index 04238b6..0fcebff 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile
@@ -34,8 +34,6 @@
 
   target 'RunnerTests' do
     inherit! :search_paths
-
-    pod 'OCMock', '~> 3.6'
   end
 end
 
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj
index e4eb1fa..86bb211 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj
@@ -265,7 +265,6 @@
 				F700DCFE28E652A10004836B /* Sources */,
 				F700DCFF28E652A10004836B /* Frameworks */,
 				F700DD0028E652A10004836B /* Resources */,
-				E318947BE753B7BBEFC3782A /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -425,23 +424,6 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		E318947BE753B7BBEFC3782A /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
 		F83C62E1BF4D0A86747FA7CF /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 53f3a37..5eb222f 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -38,8 +38,7 @@
       </MacroExpansion>
       <Testables>
          <TestableReference
-            skipped = "NO"
-            parallelizable = "NO">
+            skipped = "NO">
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "F700DD0128E652A10004836B"
@@ -70,6 +69,9 @@
             ReferencedContainer = "container:Runner.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
+      <StoreKitConfigurationFileReference
+         identifier = "../../ios/Runner/Configuration.storekit">
+      </StoreKitConfigurationFileReference>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Profile"
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata
index 21a3cc1..7f167c1 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,9 @@
 <Workspace
    version = "1.0">
    <FileRef
+      location = "group:../ios/Runner/Configuration.storekit">
+   </FileRef>
+   <FileRef
       location = "group:Runner.xcodeproj">
    </FileRef>
    <FileRef
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m
index 187cc6e..1056c34 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import <OCMock/OCMock.h>
 #import <XCTest/XCTest.h>
 #import "FIAObjectTranslator.h"
 #import "FIAPaymentQueueHandler.h"
@@ -14,17 +13,14 @@
 API_UNAVAILABLE(tvos, macos, watchos)
 @interface FIAPPaymentQueueDelegateTests : XCTestCase
 
-@property(strong, nonatomic) FlutterMethodChannel *channel;
-@property(strong, nonatomic) SKPaymentTransaction *transaction;
-@property(strong, nonatomic) SKStorefront *storefront;
+@property(nonatomic, strong) SKPaymentTransaction *transaction;
+@property(nonatomic, strong) SKStorefront *storefront;
 
 @end
 
 @implementation FIAPPaymentQueueDelegateTests
 
 - (void)setUp {
-  self.channel = OCMClassMock(FlutterMethodChannel.class);
-
   NSDictionary *transactionMap = @{
     @"transactionIdentifier" : [NSNull null],
     @"transactionState" : @(SKPaymentTransactionStatePurchasing),
@@ -45,21 +41,24 @@
 }
 
 - (void)tearDown {
-  self.channel = nil;
 }
 
 - (void)testShouldContinueTransaction {
   if (@available(iOS 13.0, *)) {
+    MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
+    channelStub.invokeMethodChannelWithResultsStub =
+        ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) {
+          XCTAssertEqualObjects(method, @"shouldContinueTransaction");
+          XCTAssertEqualObjects(arguments,
+                                [FIAObjectTranslator getMapFromSKStorefront:self.storefront
+                                                    andSKPaymentTransaction:self.transaction]);
+          result(@NO);
+        };
+
     FIAPPaymentQueueDelegate *delegate =
-        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub];
 
-    OCMStub([self.channel
-        invokeMethod:@"shouldContinueTransaction"
-           arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront
-                                         andSKPaymentTransaction:self.transaction]
-              result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]);
-
-    BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class)
+    BOOL shouldContinue = [delegate paymentQueue:[[SKPaymentQueueStub alloc] init]
                        shouldContinueTransaction:self.transaction
                                     inStorefront:self.storefront];
 
@@ -69,15 +68,19 @@
 
 - (void)testShouldContinueTransaction_should_default_to_yes {
   if (@available(iOS 13.0, *)) {
+    MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
     FIAPPaymentQueueDelegate *delegate =
-        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub];
 
-    OCMStub([self.channel invokeMethod:@"shouldContinueTransaction"
-                             arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront
-                                                           andSKPaymentTransaction:self.transaction]
-                                result:[OCMArg any]]);
+    channelStub.invokeMethodChannelWithResultsStub =
+        ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) {
+          XCTAssertEqualObjects(method, @"shouldContinueTransaction");
+          XCTAssertEqualObjects(arguments,
+                                [FIAObjectTranslator getMapFromSKStorefront:self.storefront
+                                                    andSKPaymentTransaction:self.transaction]);
+        };
 
-    BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class)
+    BOOL shouldContinue = [delegate paymentQueue:[[SKPaymentQueueStub alloc] init]
                        shouldContinueTransaction:self.transaction
                                     inStorefront:self.storefront];
 
@@ -88,16 +91,19 @@
 #if TARGET_OS_IOS
 - (void)testShouldShowPriceConsentIfNeeded {
   if (@available(iOS 13.4, *)) {
+    MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
     FIAPPaymentQueueDelegate *delegate =
-        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub];
 
-    OCMStub([self.channel
-        invokeMethod:@"shouldShowPriceConsent"
-           arguments:nil
-              result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]);
+    channelStub.invokeMethodChannelWithResultsStub =
+        ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) {
+          XCTAssertEqualObjects(method, @"shouldShowPriceConsent");
+          XCTAssertNil(arguments);
+          result(@NO);
+        };
 
     BOOL shouldShow =
-        [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)];
+        [delegate paymentQueueShouldShowPriceConsent:[[SKPaymentQueueStub alloc] init]];
 
     XCTAssertFalse(shouldShow);
   }
@@ -107,15 +113,18 @@
 #if TARGET_OS_IOS
 - (void)testShouldShowPriceConsentIfNeeded_should_default_to_yes {
   if (@available(iOS 13.4, *)) {
+    MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
     FIAPPaymentQueueDelegate *delegate =
-        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub];
 
-    OCMStub([self.channel invokeMethod:@"shouldShowPriceConsent"
-                             arguments:nil
-                                result:[OCMArg any]]);
+    channelStub.invokeMethodChannelWithResultsStub =
+        ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) {
+          XCTAssertEqualObjects(method, @"shouldShowPriceConsent");
+          XCTAssertNil(arguments);
+        };
 
     BOOL shouldShow =
-        [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)];
+        [delegate paymentQueueShouldShowPriceConsent:[[SKPaymentQueueStub alloc] init]];
 
     XCTAssertTrue(shouldShow);
   }
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m
index 8839bd2..1820ff8 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import <OCMock/OCMock.h>
 #import <XCTest/XCTest.h>
 #import "FIAPaymentQueueHandler.h"
 #import "RunnerTests-Swift.h"
@@ -12,8 +11,8 @@
 
 @interface InAppPurchasePluginTest : XCTestCase
 
-@property(strong, nonatomic) FIAPReceiptManagerStub *receiptManagerStub;
-@property(strong, nonatomic) InAppPurchasePlugin *plugin;
+@property(nonatomic, strong) FIAPReceiptManagerStub *receiptManagerStub;
+@property(nonatomic, strong) InAppPurchasePlugin *plugin;
 
 @end
 
@@ -23,8 +22,9 @@
   self.receiptManagerStub = [FIAPReceiptManagerStub new];
   self.plugin = [[InAppPurchasePluginStub alloc]
       initWithReceiptManager:self.receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return [[FIAPRequestHandler alloc] initWithRequest:request];
+              handlerFactory:^DefaultRequestHandler *(SKRequest *request) {
+                return [[DefaultRequestHandler alloc]
+                    initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]];
               }];
 }
 
@@ -40,23 +40,23 @@
 
 - (void)testPaymentQueueStorefront {
   if (@available(iOS 13, macOS 10.15, *)) {
-    SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class);
     NSDictionary *storefrontMap = @{
       @"countryCode" : @"USA",
       @"identifier" : @"unique_identifier",
     };
+    PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
+    TransactionCacheStub *cache = [[TransactionCacheStub alloc] init];
 
-    OCMStub(mockQueue.storefront).andReturn([[SKStorefrontStub alloc] initWithMap:storefrontMap]);
+    queueStub.storefront = [[SKStorefrontStub alloc] initWithMap:storefrontMap];
 
-    self.plugin.paymentQueueHandler =
-        [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue
-                                  transactionsUpdated:nil
-                                   transactionRemoved:nil
-                             restoreTransactionFailed:nil
-                 restoreCompletedTransactionsFinished:nil
-                                shouldAddStorePayment:nil
-                                     updatedDownloads:nil
-                                     transactionCache:OCMClassMock(FIATransactionCache.class)];
+    self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                                transactionsUpdated:nil
+                                                                 transactionRemoved:nil
+                                                           restoreTransactionFailed:nil
+                                               restoreCompletedTransactionsFinished:nil
+                                                              shouldAddStorePayment:nil
+                                                                   updatedDownloads:nil
+                                                                   transactionCache:cache];
 
     FlutterError *error;
     SKStorefrontMessage *result = [self.plugin storefrontWithError:&error];
@@ -71,19 +71,17 @@
 
 - (void)testPaymentQueueStorefrontReturnsNil {
   if (@available(iOS 13, macOS 10.15, *)) {
-    SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class);
+    PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
+    TransactionCacheStub *cache = [[TransactionCacheStub alloc] init];
 
-    OCMStub(mockQueue.storefront).andReturn(nil);
-
-    self.plugin.paymentQueueHandler =
-        [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue
-                                  transactionsUpdated:nil
-                                   transactionRemoved:nil
-                             restoreTransactionFailed:nil
-                 restoreCompletedTransactionsFinished:nil
-                                shouldAddStorePayment:nil
-                                     updatedDownloads:nil
-                                     transactionCache:OCMClassMock(FIATransactionCache.class)];
+    self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                                transactionsUpdated:nil
+                                                                 transactionRemoved:nil
+                                                           restoreTransactionFailed:nil
+                                               restoreCompletedTransactionsFinished:nil
+                                                              shouldAddStorePayment:nil
+                                                                   updatedDownloads:nil
+                                                                   transactionCache:cache];
 
     FlutterError *error;
     SKStorefrontMessage *resultMap = [self.plugin storefrontWithError:&error];
@@ -129,14 +127,23 @@
     @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970),
   };
 
-  SKPaymentTransactionStub *paymentTransaction =
+  SKPaymentTransactionStub *paymentTransactionStub =
       [[SKPaymentTransactionStub alloc] initWithMap:transactionMap];
-  NSArray *array = @[ paymentTransaction ];
+  NSArray *array = @[ paymentTransactionStub ];
 
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array);
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
+  queue.transactions = array;
 
-  self.plugin.paymentQueueHandler = mockHandler;
+  TransactionCacheStub *cache = [[TransactionCacheStub alloc] init];
+
+  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
+                                                              transactionsUpdated:nil
+                                                               transactionRemoved:nil
+                                                         restoreTransactionFailed:nil
+                                             restoreCompletedTransactionsFinished:nil
+                                                            shouldAddStorePayment:nil
+                                                                 updatedDownloads:nil
+                                                                 transactionCache:cache];
 
   FlutterError *error;
   [self.plugin finishTransactionFinishMap:args error:&error];
@@ -167,13 +174,23 @@
     @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970),
   };
 
-  SKPaymentTransactionStub *paymentTransaction =
+  SKPaymentTransactionStub *paymentTransactionStub =
       [[SKPaymentTransactionStub alloc] initWithMap:transactionMap];
 
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[ paymentTransaction ]);
+  PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
+  queueStub.transactions = @[ paymentTransactionStub ];
 
-  self.plugin.paymentQueueHandler = mockHandler;
+  TransactionCacheStub *cache = [[TransactionCacheStub alloc] init];
+
+  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                              transactionsUpdated:nil
+                                                               transactionRemoved:nil
+                                                         restoreTransactionFailed:nil
+                                             restoreCompletedTransactionsFinished:nil
+                                                            shouldAddStorePayment:nil
+                                                                 updatedDownloads:nil
+                                                                 transactionCache:cache];
+  ;
 
   FlutterError *error;
   [self.plugin finishTransactionFinishMap:args error:&error];
@@ -186,20 +203,21 @@
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"completion handler successfully called"];
 
-  id mockHandler = OCMClassMock([FIAPRequestHandler class]);
+  RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init];
   InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc]
       initWithReceiptManager:_receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return mockHandler;
+              handlerFactory:^RequestHandlerStub *(SKRequest *request) {
+                return handlerStub;
               }];
 
   NSError *error = [NSError errorWithDomain:@"errorDomain"
                                        code:0
                                    userInfo:@{NSLocalizedDescriptionKey : @"description"}];
 
-  OCMStub([mockHandler
-      startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error,
-                                                                            nil])]);
+  handlerStub.startProductRequestWithCompletionHandlerStub =
+      ^(ProductRequestCompletion _Nonnull completion) {
+        completion(nil, error);
+      };
 
   [plugin
       startProductRequestProductIdentifiers:argument
@@ -220,28 +238,23 @@
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"completion handler successfully called"];
 
-  id mockHandler = OCMClassMock([FIAPRequestHandler class]);
-
+  RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init];
   InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc]
       initWithReceiptManager:_receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return mockHandler;
+              handlerFactory:^RequestHandlerStub *(SKRequest *request) {
+                return handlerStub;
               }];
 
-  NSError *error = [NSError errorWithDomain:@"errorDomain"
-                                       code:0
-                                   userInfo:@{NSLocalizedDescriptionKey : @"description"}];
-
-  OCMStub([mockHandler
-      startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null],
-                                                                            [NSNull null], nil])]);
+  handlerStub.startProductRequestWithCompletionHandlerStub =
+      ^(ProductRequestCompletion _Nonnull completion) {
+        completion(nil, nil);
+      };
 
   [plugin
       startProductRequestProductIdentifiers:argument
                                  completion:^(SKProductsResponseMessage *_Nullable response,
                                               FlutterError *_Nullable startProductRequestError) {
                                    [expectation fulfill];
-                                   XCTAssertNotNil(error);
                                    XCTAssertNotNil(startProductRequestError);
                                    XCTAssertEqualObjects(startProductRequestError.code,
                                                          @"storekit_platform_no_response");
@@ -256,15 +269,20 @@
     @"simulatesAskToBuyInSandbox" : @YES,
   };
 
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(NO);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
 
   FlutterError *error;
 
+  __block NSInteger addPaymentInvokeCount = 0;
+  handlerStub.addPaymentStub = ^(SKPayment *payment) {
+    addPaymentInvokeCount += 1;
+    return NO;
+  };
+
   [self.plugin addPaymentPaymentMap:argument error:&error];
 
-  OCMVerify(times(1), [mockHandler addPayment:[OCMArg any]]);
+  XCTAssertEqual(addPaymentInvokeCount, 1);
   XCTAssertEqualObjects(@"storekit_duplicate_product_object", error.code);
   XCTAssertEqualObjects(@"There is a pending transaction for the same product identifier. "
                         @"Please either wait for it to be finished or finish it manually "
@@ -301,21 +319,24 @@
     @"simulatesAskToBuyInSandbox" : @YES,
   };
 
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
+
+  __block NSInteger addPaymentInvokeCount = 0;
+  handlerStub.addPaymentStub = ^(SKPayment *payment) {
+    XCTAssert(payment != nil);
+    XCTAssertEqual(payment.productIdentifier, @"123");
+    XCTAssert(payment.quantity == 1);
+    addPaymentInvokeCount++;
+    return YES;
+  };
+
   FlutterError *error;
 
   [self.plugin addPaymentPaymentMap:argument error:&error];
 
   XCTAssertNil(error);
-  OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) {
-                                     SKPayment *payment = obj;
-                                     XCTAssert(payment != nil);
-                                     XCTAssertEqual(payment.productIdentifier, @"123");
-                                     XCTAssert(payment.quantity == 1);
-                                     return YES;
-                                   }]]);
+  XCTAssertEqual(addPaymentInvokeCount, 1);
 }
 
 - (void)testAddPaymentSuccessWithPaymentDiscount {
@@ -332,43 +353,42 @@
     }
   };
 
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
+
+  __block NSInteger addPaymentInvokeCount = 0;
+  handlerStub.addPaymentStub = ^(SKPayment *payment) {
+    if (@available(iOS 12.2, *)) {
+      SKPaymentDiscount *discount = payment.paymentDiscount;
+      XCTAssertEqual(discount.identifier, @"test_identifier");
+      XCTAssertEqual(discount.keyIdentifier, @"test_key_identifier");
+      XCTAssertEqualObjects(
+          discount.nonce,
+          [[NSUUID alloc] initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]);
+      XCTAssertEqual(discount.signature, @"test_signature");
+      addPaymentInvokeCount++;
+      return YES;
+    }
+    addPaymentInvokeCount++;
+    return YES;
+  };
 
   FlutterError *error;
 
   [self.plugin addPaymentPaymentMap:argument error:&error];
+  XCTAssertEqual(addPaymentInvokeCount, 1);
   XCTAssertNil(error);
-  OCMVerify(
-      times(1),
-      [mockHandler
-          addPayment:[OCMArg checkWithBlock:^BOOL(id obj) {
-            SKPayment *payment = obj;
-            if (@available(iOS 12.2, *)) {
-              SKPaymentDiscount *discount = payment.paymentDiscount;
-
-              return [discount.identifier isEqual:@"test_identifier"] &&
-                     [discount.keyIdentifier isEqual:@"test_key_identifier"] &&
-                     [discount.nonce
-                         isEqual:[[NSUUID alloc]
-                                     initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]] &&
-                     [discount.signature isEqual:@"test_signature"] &&
-                     [discount.timestamp isEqual:@(1635847102)];
-            }
-
-            return YES;
-          }]]);
 }
 
 - (void)testAddPaymentFailureWithInvalidPaymentDiscount {
   // Support for payment discount is only available on iOS 12.2 and higher.
   if (@available(iOS 12.2, *)) {
-    NSDictionary *argument = @{
+    NSDictionary *invalidDiscount = @{
       @"productIdentifier" : @"123",
       @"quantity" : @(1),
       @"simulatesAskToBuyInSandbox" : @YES,
       @"paymentDiscount" : @{
+        /// This payment discount is missing the field `identifier`, and is thus malformed
         @"keyIdentifier" : @"test_key_identifier",
         @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003",
         @"signature" : @"test_signature",
@@ -376,25 +396,26 @@
       }
     };
 
-    FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-    id translator = OCMClassMock(FIAObjectTranslator.class);
+    PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
 
-    NSString *errorMsg = @"Some error occurred";
-    OCMStub(ClassMethod([translator
-                getSKPaymentDiscountFromMap:[OCMArg any]
-                                  withError:(NSString __autoreleasing **)[OCMArg setTo:errorMsg]]))
-        .andReturn(nil);
-    self.plugin.paymentQueueHandler = mockHandler;
+    __block NSInteger addPaymentCount = 0;
+    handlerStub.addPaymentStub = ^BOOL(SKPayment *_Nonnull payment) {
+      addPaymentCount++;
+      return YES;
+    };
+
+    self.plugin.paymentQueueHandler = handlerStub;
     FlutterError *error;
 
-    [self.plugin addPaymentPaymentMap:argument error:&error];
+    [self.plugin addPaymentPaymentMap:invalidDiscount error:&error];
 
     XCTAssertEqualObjects(@"storekit_invalid_payment_discount_object", error.code);
     XCTAssertEqualObjects(@"You have requested a payment and specified a "
-                          @"payment discount with invalid properties. Some error occurred",
+                          @"payment discount with invalid properties. When specifying a payment "
+                          @"discount the 'identifier' field is mandatory.",
                           error.message);
-    XCTAssertEqualObjects(argument, error.details);
-    OCMVerify(never(), [mockHandler addPayment:[OCMArg any]]);
+    XCTAssertEqualObjects(invalidDiscount, error.details);
+    XCTAssertEqual(0, addPaymentCount);
   }
 }
 
@@ -405,27 +426,30 @@
     @"simulatesAskToBuyInSandbox" : [NSNull null],
   };
 
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
   FlutterError *error;
 
+  __block NSInteger addPaymentInvokeCount = 0;
+  handlerStub.addPaymentStub = ^(SKPayment *payment) {
+    XCTAssertEqual(payment.simulatesAskToBuyInSandbox, false);
+    addPaymentInvokeCount++;
+    return YES;
+  };
+
   [self.plugin addPaymentPaymentMap:argument error:&error];
-  OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) {
-                                     SKPayment *payment = obj;
-                                     return !payment.simulatesAskToBuyInSandbox;
-                                   }]]);
+  XCTAssertEqual(addPaymentInvokeCount, 1);
 }
 
 - (void)testRestoreTransactions {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"result successfully restore transactions"];
 
-  SKPaymentQueueStub *queue = [SKPaymentQueueStub new];
-  queue.testState = SKPaymentTransactionStatePurchased;
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
+  PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
 
   __block BOOL callbackInvoked = NO;
-  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
+  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
       transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
       }
       transactionRemoved:nil
@@ -436,8 +460,8 @@
       }
       shouldAddStorePayment:nil
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
-  [queue addTransactionObserver:self.plugin.paymentQueueHandler];
+      transactionCache:cacheStub];
+  [queueStub addTransactionObserver:self.plugin.paymentQueueHandler];
 
   FlutterError *error;
   [self.plugin restoreTransactionsApplicationUserName:nil error:&error];
@@ -454,8 +478,8 @@
 }
 
 - (void)testRetrieveReceiptDataNil {
-  NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]);
-  OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil);
+  self.receiptManagerStub.returnNilURL = YES;
+
   FlutterError *error;
   NSString *result = [self.plugin retrieveReceiptDataWithError:&error];
   XCTAssertNil(result);
@@ -480,20 +504,21 @@
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"completion handler successfully called"];
 
-  id mockHandler = OCMClassMock([FIAPRequestHandler class]);
+  RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init];
   InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc]
       initWithReceiptManager:_receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return mockHandler;
+              handlerFactory:^RequestHandlerStub *(SKRequest *request) {
+                return handlerStub;
               }];
 
   NSError *recieptError = [NSError errorWithDomain:@"errorDomain"
                                               code:0
                                           userInfo:@{NSLocalizedDescriptionKey : @"description"}];
 
-  OCMStub([mockHandler
-      startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null],
-                                                                            recieptError, nil])]);
+  handlerStub.startProductRequestWithCompletionHandlerStub =
+      ^(ProductRequestCompletion _Nonnull completion) {
+        completion(nil, recieptError);
+      };
 
   [plugin refreshReceiptReceiptProperties:nil
                                completion:^(FlutterError *_Nullable error) {
@@ -512,20 +537,21 @@
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"completion handler successfully called"];
 
-  id mockHandler = OCMClassMock([FIAPRequestHandler class]);
+  RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init];
   InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc]
       initWithReceiptManager:_receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return mockHandler;
+              handlerFactory:^RequestHandlerStub *(SKRequest *request) {
+                return handlerStub;
               }];
 
   NSError *recieptError = [NSError errorWithDomain:@"errorDomain"
                                               code:0
                                           userInfo:@{NSLocalizedDescriptionKey : @"description"}];
 
-  OCMStub([mockHandler
-      startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null],
-                                                                            recieptError, nil])]);
+  handlerStub.startProductRequestWithCompletionHandlerStub =
+      ^(ProductRequestCompletion _Nonnull completion) {
+        completion(nil, recieptError);
+      };
 
   [plugin refreshReceiptReceiptProperties:properties
                                completion:^(FlutterError *_Nullable error) {
@@ -544,20 +570,21 @@
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"completion handler successfully called"];
 
-  id mockHandler = OCMClassMock([FIAPRequestHandler class]);
+  RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init];
   InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc]
       initWithReceiptManager:_receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return mockHandler;
+              handlerFactory:^RequestHandlerStub *(SKRequest *request) {
+                return handlerStub;
               }];
 
   NSError *recieptError = [NSError errorWithDomain:@"errorDomain"
                                               code:0
                                           userInfo:@{NSLocalizedDescriptionKey : @"description"}];
 
-  OCMStub([mockHandler
-      startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null],
-                                                                            recieptError, nil])]);
+  handlerStub.startProductRequestWithCompletionHandlerStub =
+      ^(ProductRequestCompletion _Nonnull completion) {
+        completion(nil, recieptError);
+      };
 
   [plugin refreshReceiptReceiptProperties:properties
                                completion:^(FlutterError *_Nullable error) {
@@ -572,18 +599,24 @@
 /// presentCodeRedemptionSheetWithError:error is only available on iOS
 #if TARGET_OS_IOS
 - (void)testPresentCodeRedemptionSheet {
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
+
+  __block NSInteger presentCodeRedemptionSheetCount = 0;
+  handlerStub.presentCodeRedemptionSheetStub = ^{
+    presentCodeRedemptionSheetCount++;
+  };
 
   FlutterError *error;
   [self.plugin presentCodeRedemptionSheetWithError:&error];
 
-  OCMVerify(times(1), [mockHandler presentCodeRedemptionSheet]);
+  XCTAssertEqual(1, presentCodeRedemptionSheetCount);
 }
 #endif
 
 - (void)testGetPendingTransactions {
-  SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class);
+  PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
   NSDictionary *transactionMap = @{
     @"transactionIdentifier" : [NSNull null],
     @"transactionState" : @(SKPaymentTransactionStatePurchasing),
@@ -594,18 +627,15 @@
     @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970),
     @"originalTransaction" : [NSNull null],
   };
-  OCMStub(mockQueue.transactions).andReturn(@[ [[SKPaymentTransactionStub alloc]
-      initWithMap:transactionMap] ]);
-
-  self.plugin.paymentQueueHandler =
-      [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue
-                                transactionsUpdated:nil
-                                 transactionRemoved:nil
-                           restoreTransactionFailed:nil
-               restoreCompletedTransactionsFinished:nil
-                              shouldAddStorePayment:nil
-                                   updatedDownloads:nil
-                                   transactionCache:OCMClassMock(FIATransactionCache.class)];
+  queueStub.transactions = @[ [[SKPaymentTransactionStub alloc] initWithMap:transactionMap] ];
+  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                              transactionsUpdated:nil
+                                                               transactionRemoved:nil
+                                                         restoreTransactionFailed:nil
+                                             restoreCompletedTransactionsFinished:nil
+                                                            shouldAddStorePayment:nil
+                                                                 updatedDownloads:nil
+                                                                 transactionCache:cacheStub];
   FlutterError *error;
   SKPaymentTransactionStub *original =
       [[SKPaymentTransactionStub alloc] initWithMap:transactionMap];
@@ -619,45 +649,50 @@
 }
 
 - (void)testStartObservingPaymentQueue {
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
+
+  __block NSInteger startObservingCount = 0;
+  handlerStub.startObservingPaymentQueueStub = ^{
+    startObservingCount++;
+  };
 
   FlutterError *error;
   [self.plugin startObservingPaymentQueueWithError:&error];
 
-  OCMVerify(times(1), [mockHandler startObservingPaymentQueue]);
+  XCTAssertEqual(1, startObservingCount);
 }
 
 - (void)testStopObservingPaymentQueue {
-  FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]);
-  self.plugin.paymentQueueHandler = mockHandler;
+  PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init];
+  self.plugin.paymentQueueHandler = handlerStub;
+
+  __block NSInteger stopObservingCount = 0;
+  handlerStub.stopObservingPaymentQueueStub = ^{
+    stopObservingCount++;
+  };
 
   FlutterError *error;
   [self.plugin stopObservingPaymentQueueWithError:&error];
 
-  OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]);
+  XCTAssertEqual(1, stopObservingCount);
 }
 
 #if TARGET_OS_IOS
 - (void)testRegisterPaymentQueueDelegate {
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
+  PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
   if (@available(iOS 13, *)) {
-    self.plugin.paymentQueueHandler =
-        [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new]
-                                  transactionsUpdated:nil
-                                   transactionRemoved:nil
-                             restoreTransactionFailed:nil
-                 restoreCompletedTransactionsFinished:nil
-                                shouldAddStorePayment:nil
-                                     updatedDownloads:nil
-                                     transactionCache:OCMClassMock(FIATransactionCache.class)];
+    self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                                transactionsUpdated:nil
+                                                                 transactionRemoved:nil
+                                                           restoreTransactionFailed:nil
+                                               restoreCompletedTransactionsFinished:nil
+                                                              shouldAddStorePayment:nil
+                                                                   updatedDownloads:nil
+                                                                   transactionCache:cacheStub];
 
-    self.plugin.registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar));
-
-    id<FlutterPluginRegistrar> registrarMock = OCMProtocolMock(@protocol(FlutterPluginRegistrar));
-    self.plugin.registrar = registrarMock;
-
-    id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
-    OCMStub([registrarMock messenger]).andReturn(binaryMessengerMock);
+    self.plugin.registrar = [[FlutterPluginRegistrarStub alloc] init];
 
     // Verify the delegate is nil before we register one.
     XCTAssertNil(self.plugin.paymentQueueHandler.delegate);
@@ -672,21 +707,28 @@
 
 - (void)testRemovePaymentQueueDelegate {
   if (@available(iOS 13, *)) {
-    self.plugin.paymentQueueHandler =
-        [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new]
-                                  transactionsUpdated:nil
-                                   transactionRemoved:nil
-                             restoreTransactionFailed:nil
-                 restoreCompletedTransactionsFinished:nil
-                                shouldAddStorePayment:nil
-                                     updatedDownloads:nil
-                                     transactionCache:OCMClassMock(FIATransactionCache.class)];
-    self.plugin.paymentQueueHandler.delegate = OCMProtocolMock(@protocol(SKPaymentQueueDelegate));
+    TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
+    PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
+    self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                                transactionsUpdated:nil
+                                                                 transactionRemoved:nil
+                                                           restoreTransactionFailed:nil
+                                               restoreCompletedTransactionsFinished:nil
+                                                              shouldAddStorePayment:nil
+                                                                   updatedDownloads:nil
+                                                                   transactionCache:cacheStub];
+
+    self.plugin.registrar = [[FlutterPluginRegistrarStub alloc] init];
+
+    // Verify the delegate is nil before we register one.
+    XCTAssertNil(self.plugin.paymentQueueHandler.delegate);
+
+    FlutterError *error;
+    [self.plugin registerPaymentQueueDelegateWithError:&error];
 
     // Verify the delegate is not nil before removing it.
     XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate);
 
-    FlutterError *error;
     [self.plugin removePaymentQueueDelegateWithError:&error];
 
     // Verify the delegate is nill after removing it.
@@ -708,21 +750,30 @@
 
   InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc]
       initWithReceiptManager:self.receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return [[FIAPRequestHandler alloc] initWithRequest:request];
+              handlerFactory:^DefaultRequestHandler *(SKRequest *request) {
+                return [[DefaultRequestHandler alloc]
+                    initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]];
               }];
-  FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  plugin.transactionObserverCallbackChannel = mockChannel;
-  OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]);
+  MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
+  __block NSInteger invokeMethodCount = 0;
 
-  SKPaymentTransactionStub *paymentTransaction =
+  channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) {
+    XCTAssertEqualObjects(@"updatedTransactions", method);
+    XCTAssertNotNil(arguments);
+    invokeMethodCount++;
+  };
+
+  // (TODO: louisehsu) Change this to inject the channel, like requestHandler
+  plugin.transactionObserverCallbackChannel = channelStub;
+
+  SKPaymentTransactionStub *paymentTransactionStub =
       [[SKPaymentTransactionStub alloc] initWithMap:transactionMap];
-  NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil];
+  NSArray *array = [NSArray arrayWithObjects:paymentTransactionStub, nil];
   NSMutableArray *maps = [NSMutableArray new];
-  [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]];
+  [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransactionStub]];
 
   [plugin handleTransactionsUpdated:array];
-  OCMVerify(times(1), [mockChannel invokeMethod:@"updatedTransactions" arguments:[OCMArg any]]);
+  XCTAssertEqual(invokeMethodCount, 1);
 }
 
 - (void)testHandleTransactionsRemoved {
@@ -738,52 +789,76 @@
 
   InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc]
       initWithReceiptManager:self.receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return [[FIAPRequestHandler alloc] initWithRequest:request];
+              handlerFactory:^DefaultRequestHandler *(SKRequest *request) {
+                return [[DefaultRequestHandler alloc]
+                    initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]];
               }];
-  FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  plugin.transactionObserverCallbackChannel = mockChannel;
-  OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]);
-
-  SKPaymentTransactionStub *paymentTransaction =
+  SKPaymentTransactionStub *paymentTransactionStub =
       [[SKPaymentTransactionStub alloc] initWithMap:transactionMap];
-  NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil];
+  NSArray *array = [NSArray arrayWithObjects:paymentTransactionStub, nil];
   NSMutableArray *maps = [NSMutableArray new];
-  [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]];
+  [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransactionStub]];
+
+  MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
+  __block NSInteger invokeMethodCount = 0;
+
+  channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) {
+    XCTAssertEqualObjects(@"removedTransactions", method);
+    XCTAssertEqualObjects(maps, arguments);
+    invokeMethodCount++;
+  };
+
+  // (TODO: louisehsu) Change this to inject the channel, like requestHandler
+  plugin.transactionObserverCallbackChannel = channelStub;
 
   [plugin handleTransactionsRemoved:array];
-  OCMVerify(times(1), [mockChannel invokeMethod:@"removedTransactions" arguments:maps]);
+  XCTAssertEqual(invokeMethodCount, 1);
 }
 
 - (void)testHandleTransactionRestoreFailed {
   InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc]
       initWithReceiptManager:self.receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return [[FIAPRequestHandler alloc] initWithRequest:request];
+              handlerFactory:^DefaultRequestHandler *(SKRequest *request) {
+                return [[DefaultRequestHandler alloc]
+                    initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]];
               }];
-  FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  plugin.transactionObserverCallbackChannel = mockChannel;
-  OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]);
-
+  MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
+  __block NSInteger invokeMethodCount = 0;
   NSError *error = [NSError errorWithDomain:@"error" code:0 userInfo:nil];
+
+  channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) {
+    XCTAssertEqualObjects(@"restoreCompletedTransactionsFailed", method);
+    XCTAssertEqualObjects([FIAObjectTranslator getMapFromNSError:error], arguments);
+    invokeMethodCount++;
+  };
+
+  // (TODO: louisehsu) Change this to inject the channel, like requestHandler
+  plugin.transactionObserverCallbackChannel = channelStub;
+
   [plugin handleTransactionRestoreFailed:error];
-  OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed"
-                                      arguments:[FIAObjectTranslator getMapFromNSError:error]]);
+  XCTAssertEqual(invokeMethodCount, 1);
 }
 
 - (void)testRestoreCompletedTransactionsFinished {
   InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc]
       initWithReceiptManager:self.receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return [[FIAPRequestHandler alloc] initWithRequest:request];
+              handlerFactory:^DefaultRequestHandler *(SKRequest *request) {
+                return [[DefaultRequestHandler alloc]
+                    initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]];
               }];
-  FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  plugin.transactionObserverCallbackChannel = mockChannel;
-  OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]);
+  MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
+  __block NSInteger invokeMethodCount = 0;
+  channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) {
+    XCTAssertEqualObjects(@"paymentQueueRestoreCompletedTransactionsFinished", method);
+    XCTAssertNil(arguments);
+    invokeMethodCount++;
+  };
+
+  // (TODO: louisehsu) Change this to inject the channel, like requestHandler
+  plugin.transactionObserverCallbackChannel = channelStub;
 
   [plugin restoreCompletedTransactionsFinished];
-  OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished"
-                                      arguments:nil]);
+  XCTAssertEqual(invokeMethodCount, 1);
 }
 
 - (void)testShouldAddStorePayment {
@@ -804,41 +879,65 @@
   };
 
   SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap];
-  SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap];
+  SKProductStub *productStub = [[SKProductStub alloc] initWithMap:productMap];
 
   InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc]
       initWithReceiptManager:self.receiptManagerStub
-              handlerFactory:^FIAPRequestHandler *(SKRequest *request) {
-                return [[FIAPRequestHandler alloc] initWithRequest:request];
+              handlerFactory:^DefaultRequestHandler *(SKRequest *request) {
+                return [[DefaultRequestHandler alloc]
+                    initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]];
               }];
-  FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  plugin.transactionObserverCallbackChannel = mockChannel;
-  OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]);
 
   NSDictionary *args = @{
     @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment],
-    @"product" : [FIAObjectTranslator getMapFromSKProduct:product]
+    @"product" : [FIAObjectTranslator getMapFromSKProduct:productStub]
   };
 
-  BOOL result = [plugin shouldAddStorePaymentWithPayment:payment product:product];
+  MethodChannelStub *channelStub = [[MethodChannelStub alloc] init];
+
+  __block NSInteger invokeMethodCount = 0;
+  channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) {
+    XCTAssertEqualObjects(@"shouldAddStorePayment", method);
+    XCTAssertEqualObjects(args, arguments);
+    invokeMethodCount++;
+  };
+
+  // (TODO: louisehsu) Change this to inject the channel, like requestHandler
+  plugin.transactionObserverCallbackChannel = channelStub;
+
+  BOOL result = [plugin shouldAddStorePaymentWithPayment:payment product:productStub];
   XCTAssertEqual(result, NO);
-  OCMVerify(times(1), [mockChannel invokeMethod:@"shouldAddStorePayment" arguments:args]);
+  XCTAssertEqual(invokeMethodCount, 1);
 }
 
 #if TARGET_OS_IOS
 - (void)testShowPriceConsentIfNeeded {
-  FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class);
-  self.plugin.paymentQueueHandler = mockQueueHandler;
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
+  PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init];
+  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub
+                                                              transactionsUpdated:nil
+                                                               transactionRemoved:nil
+                                                         restoreTransactionFailed:nil
+                                             restoreCompletedTransactionsFinished:nil
+                                                            shouldAddStorePayment:nil
+                                                                 updatedDownloads:nil
+                                                                 transactionCache:cacheStub];
 
   FlutterError *error;
+  __block NSInteger showPriceConsentIfNeededCount = 0;
+
+  queueStub.showPriceConsentIfNeededStub = ^(void) {
+    showPriceConsentIfNeededCount++;
+  };
+
   [self.plugin showPriceConsentIfNeededWithError:&error];
 
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wpartial-availability"
   if (@available(iOS 13.4, *)) {
-    OCMVerify(times(1), [mockQueueHandler showPriceConsentIfNeeded]);
+    XCTAssertEqual(showPriceConsentIfNeededCount, 1);
   } else {
-    OCMVerify(never(), [mockQueueHandler showPriceConsentIfNeeded]);
+    XCTAssertEqual(showPriceConsentIfNeededCount, 0);
   }
 #pragma clang diagnostic pop
 }
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m
index 2f8d585..0c6d51a 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import <OCMock/OCMock.h>
 #import <XCTest/XCTest.h>
 #import "Stubs.h"
 
@@ -10,10 +9,10 @@
 
 @interface PaymentQueueTest : XCTestCase
 
-@property(strong, nonatomic) NSDictionary *periodMap;
-@property(strong, nonatomic) NSDictionary *discountMap;
-@property(strong, nonatomic) NSDictionary *productMap;
-@property(strong, nonatomic) NSDictionary *productResponseMap;
+@property(nonatomic, strong) NSDictionary *periodMap;
+@property(nonatomic, strong) NSDictionary *discountMap;
+@property(nonatomic, strong) NSDictionary *productMap;
+@property(nonatomic, strong) NSDictionary *productResponseMap;
 
 @end
 
@@ -45,7 +44,7 @@
 - (void)testTransactionPurchased {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"expect to get purchased transcation."];
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStatePurchased;
   __block SKPaymentTransactionStub *tran;
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
@@ -61,20 +60,20 @@
         return YES;
       }
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
+      transactionCache:[[TransactionCacheStub alloc] init]];
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
   [handler startObservingPaymentQueue];
   [handler addPayment:payment];
   [self waitForExpectations:@[ expectation ] timeout:5];
   XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased);
-  XCTAssertEqual(tran.transactionIdentifier, @"fakeID");
+  XCTAssertEqualObjects(tran.transactionIdentifier, @"fakeID");
 }
 
 - (void)testTransactionFailed {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"expect to get failed transcation."];
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStateFailed;
   __block SKPaymentTransactionStub *tran;
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
@@ -90,7 +89,7 @@
         return YES;
       }
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
+      transactionCache:[[TransactionCacheStub alloc] init]];
 
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
@@ -104,7 +103,7 @@
 - (void)testTransactionRestored {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"expect to get restored transcation."];
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStateRestored;
   __block SKPaymentTransactionStub *tran;
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
@@ -120,7 +119,7 @@
         return YES;
       }
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
+      transactionCache:[[TransactionCacheStub alloc] init]];
 
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
@@ -128,13 +127,13 @@
   [handler addPayment:payment];
   [self waitForExpectations:@[ expectation ] timeout:5];
   XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored);
-  XCTAssertEqual(tran.transactionIdentifier, @"fakeID");
+  XCTAssertEqualObjects(tran.transactionIdentifier, @"fakeID");
 }
 
 - (void)testTransactionPurchasing {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"expect to get purchasing transcation."];
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStatePurchasing;
   __block SKPaymentTransactionStub *tran;
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
@@ -150,7 +149,7 @@
         return YES;
       }
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
+      transactionCache:[[TransactionCacheStub alloc] init]];
 
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
@@ -164,7 +163,7 @@
 - (void)testTransactionDeferred {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"expect to get deffered transcation."];
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStateDeferred;
   __block SKPaymentTransactionStub *tran;
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
@@ -180,7 +179,7 @@
         return YES;
       }
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
+      transactionCache:[[TransactionCacheStub alloc] init]];
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
   [handler startObservingPaymentQueue];
@@ -193,7 +192,7 @@
 - (void)testFinishTransaction {
   XCTestExpectation *expectation =
       [self expectationWithDescription:@"handler.transactions should be empty."];
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStateDeferred;
   __block FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
       transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
@@ -211,7 +210,7 @@
         return YES;
       }
       updatedDownloads:nil
-      transactionCache:OCMClassMock(FIATransactionCache.class)];
+      transactionCache:[[TransactionCacheStub alloc] init]];
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
   [handler startObservingPaymentQueue];
@@ -220,9 +219,9 @@
 }
 
 - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmpty {
-  FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
   FIAPaymentQueueHandler *handler =
-      [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
+      [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init]
           transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
             XCTFail("transactionsUpdated callback should not be called when cache is empty.");
           }
@@ -237,20 +236,41 @@
           updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
             XCTFail("updatedDownloads callback should not be called when cache is empty.");
           }
-          transactionCache:mockCache];
+          transactionCache:cacheStub];
+
+  __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0;
+
+  cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) {
+    switch (key) {
+      case TransactionCacheKeyUpdatedTransactions:
+        TransactionCacheKeyUpdatedTransactionsInvokedCount++;
+        break;
+      case TransactionCacheKeyUpdatedDownloads:
+        TransactionCacheKeyUpdatedDownloadsInvokedCount++;
+        break;
+      case TransactionCacheKeyRemovedTransactions:
+        TransactionCacheKeyRemovedTransactionsInvokedCount++;
+        break;
+      default:
+        XCTFail("Invalid transaction state was invoked.");
+    }
+    return nil;
+  };
 
   [handler startObservingPaymentQueue];
 
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount);
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount);
+  XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount);
 }
 
 - (void)
     testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheContainsEmptyTransactionArrays {
-  FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
   FIAPaymentQueueHandler *handler =
-      [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
+      [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init]
           transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
             XCTFail("transactionsUpdated callback should not be called when cache is empty.");
           }
@@ -265,17 +285,36 @@
           updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
             XCTFail("updatedDownloads callback should not be called when cache is empty.");
           }
-          transactionCache:mockCache];
+          transactionCache:cacheStub];
 
-  OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[]);
-  OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[]);
-  OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[]);
+  __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0;
+
+  cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) {
+    switch (key) {
+      case TransactionCacheKeyUpdatedTransactions:
+        TransactionCacheKeyUpdatedTransactionsInvokedCount++;
+        return @[];
+        break;
+      case TransactionCacheKeyUpdatedDownloads:
+        TransactionCacheKeyUpdatedDownloadsInvokedCount++;
+        return @[];
+        break;
+      case TransactionCacheKeyRemovedTransactions:
+        TransactionCacheKeyRemovedTransactionsInvokedCount++;
+        return @[];
+        break;
+      default:
+        XCTFail("Invalid transaction state was invoked.");
+    }
+  };
 
   [handler startObservingPaymentQueue];
 
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount);
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount);
+  XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount);
 }
 
 - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache {
@@ -288,17 +327,17 @@
   XCTestExpectation *updateDownloadsExpectation =
       [self expectationWithDescription:
                 @"downloadsUpdated callback should be called with one transaction."];
-  SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class);
-  SKDownload *mockDownload = OCMClassMock(SKDownload.class);
-  FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
+  SKPaymentTransaction *transactionStub = [[SKPaymentTransactionStub alloc] init];
+  SKDownload *downloadStub = [[SKDownload alloc] init];
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
   FIAPaymentQueueHandler *handler =
-      [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
+      [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init]
           transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
-            XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
+            XCTAssertEqualObjects(transactions, @[ transactionStub ]);
             [updateTransactionsExpectation fulfill];
           }
           transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
-            XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
+            XCTAssertEqualObjects(transactions, @[ transactionStub ]);
             [removeTransactionsExpectation fulfill];
           }
           restoreTransactionFailed:nil
@@ -307,20 +346,38 @@
             return YES;
           }
           updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
-            XCTAssertEqualObjects(downloads, @[ mockDownload ]);
+            XCTAssertEqualObjects(downloads, @[ downloadStub ]);
             [updateDownloadsExpectation fulfill];
           }
-          transactionCache:mockCache];
+          transactionCache:cacheStub];
 
-  OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[
-    mockTransaction
-  ]);
-  OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[
-    mockDownload
-  ]);
-  OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[
-    mockTransaction
-  ]);
+  __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0;
+
+  cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) {
+    switch (key) {
+      case TransactionCacheKeyUpdatedTransactions:
+        TransactionCacheKeyUpdatedTransactionsInvokedCount++;
+        return @[ transactionStub ];
+        break;
+      case TransactionCacheKeyUpdatedDownloads:
+        TransactionCacheKeyUpdatedDownloadsInvokedCount++;
+        return @[ downloadStub ];
+        break;
+      case TransactionCacheKeyRemovedTransactions:
+        TransactionCacheKeyRemovedTransactionsInvokedCount++;
+        return @[ transactionStub ];
+        break;
+      default:
+        XCTFail("Invalid transaction state was invoked.");
+    }
+  };
+
+  __block NSInteger clearInvokedCount = 0;
+  cacheStub.clearStub = ^{
+    clearInvokedCount++;
+  };
 
   [handler startObservingPaymentQueue];
 
@@ -328,15 +385,16 @@
     updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation
   ]
                     timeout:5];
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
-  OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
-  OCMVerify(times(1), [mockCache clear]);
+
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount);
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount);
+  XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount);
+  XCTAssertEqual(1, clearInvokedCount);
 }
 
 - (void)testTransactionsShouldBeCachedWhenNotObserving {
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
-  FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
       transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
         XCTFail("transactionsUpdated callback should not be called when cache is empty.");
@@ -352,18 +410,36 @@
       updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
         XCTFail("updatedDownloads callback should not be called when cache is empty.");
       }
-      transactionCache:mockCache];
+      transactionCache:cacheStub];
 
   SKPayment *payment =
       [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
+
+  __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0;
+
+  cacheStub.addObjectsStub = ^(NSArray *_Nonnull objects, TransactionCacheKey key) {
+    switch (key) {
+      case TransactionCacheKeyUpdatedTransactions:
+        TransactionCacheKeyUpdatedTransactionsInvokedCount++;
+        break;
+      case TransactionCacheKeyUpdatedDownloads:
+        TransactionCacheKeyUpdatedDownloadsInvokedCount++;
+        break;
+      case TransactionCacheKeyRemovedTransactions:
+        TransactionCacheKeyRemovedTransactionsInvokedCount++;
+        break;
+      default:
+        XCTFail("Invalid transaction state was invoked.");
+    }
+  };
+
   [handler addPayment:payment];
 
-  OCMVerify(times(1), [mockCache addObjects:[OCMArg any]
-                                     forKey:TransactionCacheKeyUpdatedTransactions]);
-  OCMVerify(never(), [mockCache addObjects:[OCMArg any]
-                                    forKey:TransactionCacheKeyUpdatedDownloads]);
-  OCMVerify(never(), [mockCache addObjects:[OCMArg any]
-                                    forKey:TransactionCacheKeyRemovedTransactions]);
+  XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount);
+  XCTAssertEqual(0, TransactionCacheKeyUpdatedDownloadsInvokedCount);
+  XCTAssertEqual(0, TransactionCacheKeyRemovedTransactionsInvokedCount);
 }
 
 - (void)testTransactionsShouldNotBeCachedWhenObserving {
@@ -376,18 +452,18 @@
   XCTestExpectation *updateDownloadsExpectation =
       [self expectationWithDescription:
                 @"downloadsUpdated callback should be called with one transaction."];
-  SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class);
-  SKDownload *mockDownload = OCMClassMock(SKDownload.class);
-  SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
+  SKPaymentTransaction *transactionStub = [[SKPaymentTransactionStub alloc] init];
+  SKDownload *downloadStub = [[SKDownload alloc] init];
+  PaymentQueueStub *queue = [[PaymentQueueStub alloc] init];
   queue.testState = SKPaymentTransactionStatePurchased;
-  FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
+  TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init];
   FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
       transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
-        XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
+        XCTAssertEqualObjects(transactions, @[ transactionStub ]);
         [updateTransactionsExpectation fulfill];
       }
       transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
-        XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
+        XCTAssertEqualObjects(transactions, @[ transactionStub ]);
         [removeTransactionsExpectation fulfill];
       }
       restoreTransactionFailed:nil
@@ -396,25 +472,44 @@
         return YES;
       }
       updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
-        XCTAssertEqualObjects(downloads, @[ mockDownload ]);
+        XCTAssertEqualObjects(downloads, @[ downloadStub ]);
         [updateDownloadsExpectation fulfill];
       }
-      transactionCache:mockCache];
+      transactionCache:cacheStub];
+
+  SKPaymentQueueStub *paymentQueueStub = [[SKPaymentQueueStub alloc] init];
 
   [handler startObservingPaymentQueue];
-  [handler paymentQueue:queue updatedTransactions:@[ mockTransaction ]];
-  [handler paymentQueue:queue removedTransactions:@[ mockTransaction ]];
-  [handler paymentQueue:queue updatedDownloads:@[ mockDownload ]];
+  [handler paymentQueue:paymentQueueStub updatedTransactions:@[ transactionStub ]];
+  [handler paymentQueue:paymentQueueStub removedTransactions:@[ transactionStub ]];
+  [handler paymentQueue:paymentQueueStub updatedDownloads:@[ downloadStub ]];
 
   [self waitForExpectations:@[
     updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation
   ]
                     timeout:5];
-  OCMVerify(never(), [mockCache addObjects:[OCMArg any]
-                                    forKey:TransactionCacheKeyUpdatedTransactions]);
-  OCMVerify(never(), [mockCache addObjects:[OCMArg any]
-                                    forKey:TransactionCacheKeyUpdatedDownloads]);
-  OCMVerify(never(), [mockCache addObjects:[OCMArg any]
-                                    forKey:TransactionCacheKeyRemovedTransactions]);
+
+  __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0;
+  __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0;
+
+  cacheStub.addObjectsStub = ^(NSArray *_Nonnull objects, TransactionCacheKey key) {
+    switch (key) {
+      case TransactionCacheKeyUpdatedTransactions:
+        TransactionCacheKeyUpdatedTransactionsInvokedCount++;
+        break;
+      case TransactionCacheKeyUpdatedDownloads:
+        TransactionCacheKeyUpdatedDownloadsInvokedCount++;
+        break;
+      case TransactionCacheKeyRemovedTransactions:
+        TransactionCacheKeyRemovedTransactionsInvokedCount++;
+        break;
+      default:
+        XCTFail("Invalid transaction state was invoked.");
+    }
+  };
+  XCTAssertEqual(0, TransactionCacheKeyUpdatedTransactionsInvokedCount);
+  XCTAssertEqual(0, TransactionCacheKeyUpdatedDownloadsInvokedCount);
+  XCTAssertEqual(0, TransactionCacheKeyRemovedTransactionsInvokedCount);
 }
 @end
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h
index daad506..8e7769c 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h
@@ -4,6 +4,12 @@
 
 #import <Foundation/Foundation.h>
 #import <StoreKit/StoreKit.h>
+#import "FIATransactionCache.h"
+#import "FLTMethodChannelProtocol.h"
+#import "FLTPaymentQueueHandlerProtocol.h"
+#import "FLTPaymentQueueProtocol.h"
+#import "FLTRequestHandlerProtocol.h"
+#import "FLTTransactionCacheProtocol.h"
 
 @import in_app_purchase_storekit;
 
@@ -24,7 +30,7 @@
 @end
 
 @interface SKProductRequestStub : SKProductsRequest
-@property(assign, nonatomic) BOOL returnError;
+@property(nonatomic, assign) BOOL returnError;
 - (instancetype)initWithProductIdentifiers:(NSSet<NSString *> *)productIdentifiers;
 - (instancetype)initWithFailureError:(NSError *)error;
 @end
@@ -33,12 +39,9 @@
 - (instancetype)initWithMap:(NSDictionary *)map;
 @end
 
-@interface SKRequestStub : SKRequest
-@end
-
 @interface SKPaymentQueueStub : SKPaymentQueue
-@property(assign, nonatomic) SKPaymentTransactionState testState;
-@property(strong, nonatomic, nullable) id<SKPaymentTransactionObserver> observer;
+@property(nonatomic, assign) SKPaymentTransactionState testState;
+@property(nonatomic, strong, nullable) id<SKPaymentTransactionObserver> observer;
 @end
 
 @interface SKPaymentTransactionStub : SKPaymentTransaction
@@ -58,7 +61,9 @@
 @interface FIAPReceiptManagerStub : FIAPReceiptManager
 // Indicates whether getReceiptData of this stub is going to return an error.
 // Setting this to true will let getReceiptData give a basic NSError and return nil.
-@property(assign, nonatomic) BOOL returnError;
+@property(nonatomic, assign) BOOL returnError;
+// Indicates whether the receipt url will be nil.
+@property(nonatomic, assign) BOOL returnNilURL;
 @end
 
 @interface SKReceiptRefreshRequestStub : SKReceiptRefreshRequest
@@ -70,4 +75,116 @@
 - (instancetype)initWithMap:(NSDictionary *)map;
 @end
 
+// An interface representing a stubbed DefaultPaymentQueue
+@interface PaymentQueueStub : NSObject <FLTPaymentQueueProtocol>
+
+// FLTPaymentQueueProtocol properties
+@property(nonatomic, assign) SKPaymentTransactionState paymentState;
+@property(nonatomic, strong, nullable) id<SKPaymentTransactionObserver> observer;
+@property(nonatomic, strong, readwrite) SKStorefront *storefront API_AVAILABLE(ios(13.0));
+@property(nonatomic, strong, readwrite) NSArray<SKPaymentTransaction *> *transactions API_AVAILABLE(
+    ios(3.0), macos(10.7), watchos(6.2), visionos(1.0));
+
+// Test Properties
+@property(nonatomic, assign)
+    SKPaymentTransactionState testState;  // Set this property to set a test Transaction state, then
+                                          // call addPayment to add it to the queue.
+@property(nonatomic, strong, nonnull)
+    SKPaymentQueue *realQueue;  // This is a reference to the real SKPaymentQueue
+
+// Stubs
+@property(nonatomic, copy, nullable) void (^showPriceConsentIfNeededStub)(void);
+@property(nonatomic, copy, nullable) void (^restoreTransactionsStub)(NSString *);
+@property(nonatomic, copy, nullable) void (^startObservingPaymentQueueStub)(void);
+@property(nonatomic, copy, nullable) void (^stopObservingPaymentQueueStub)(void);
+@property(nonatomic, copy, nullable) void (^presentCodeRedemptionSheetStub)(void);
+@property(nonatomic, copy, nullable)
+    NSArray<SKPaymentTransaction *> * (^getUnfinishedTransactionsStub)(void);
+
+@end
+
+// An interface representing a stubbed DefaultTransactionCache
+@interface TransactionCacheStub : NSObject <FLTTransactionCacheProtocol>
+
+// Stubs
+@property(nonatomic, copy, nullable) NSArray * (^getObjectsForKeyStub)(TransactionCacheKey key);
+@property(nonatomic, copy, nullable) void (^clearStub)(void);
+@property(nonatomic, copy, nullable) void (^addObjectsStub)(NSArray *, TransactionCacheKey);
+
+@end
+
+// An interface representing a stubbed DefaultMethodChannel
+@interface MethodChannelStub : NSObject <FLTMethodChannelProtocol>
+
+// Stubs
+@property(nonatomic, copy, nullable) void (^invokeMethodChannelStub)(NSString *method, id arguments)
+    ;
+@property(nonatomic, copy, nullable) void (^invokeMethodChannelWithResultsStub)
+    (NSString *method, id arguments, FlutterResult _Nullable);
+
+@end
+
+// An interface representing a stubbed DefaultPaymentQueueHandler
+@interface PaymentQueueHandlerStub
+    : NSObject <SKPaymentTransactionObserver, FLTPaymentQueueHandlerProtocol>
+
+// Stubs
+@property(nonatomic, copy, nullable) BOOL (^addPaymentStub)(SKPayment *payment);
+@property(nonatomic, copy, nullable) void (^showPriceConsentIfNeededStub)(void);
+@property(nonatomic, copy, nullable) void (^stopObservingPaymentQueueStub)(void);
+@property(nonatomic, copy, nullable) void (^startObservingPaymentQueueStub)(void);
+@property(nonatomic, copy, nullable) void (^presentCodeRedemptionSheetStub)(void);
+@property(nonatomic, copy, nullable) void (^restoreTransactions)(NSString *);
+@property(nonatomic, copy, nullable)
+    NSArray<SKPaymentTransaction *> * (^getUnfinishedTransactionsStub)(void);
+@property(nonatomic, copy, nullable) void (^finishTransactionStub)(SKPaymentTransaction *);
+@property(nonatomic, copy, nullable) void (^paymentQueueUpdatedTransactionsStub)
+    (SKPaymentQueue *, NSArray<SKPaymentTransaction *> *);
+
+@end
+
+// An interface representing a stubbed DefaultRequestHandler
+@interface RequestHandlerStub : NSObject <FLTRequestHandlerProtocol>
+
+// Stubs
+@property(nonatomic, copy, nullable) void (^startProductRequestWithCompletionHandlerStub)
+    (ProductRequestCompletion);
+
+@end
+
+#if TARGET_OS_IOS
+@interface FlutterPluginRegistrarStub : NSObject <FlutterPluginRegistrar>
+
+// Stubs
+@property(nonatomic, copy, nullable) void (^addApplicationDelegateStub)(NSObject<FlutterPlugin> *);
+@property(nonatomic, copy, nullable) void (^addMethodCallDelegateStub)
+    (NSObject<FlutterPlugin> *, FlutterMethodChannel *);
+@property(nonatomic, copy, nullable) NSString * (^lookupKeyForAssetStub)(NSString *);
+@property(nonatomic, copy, nullable) NSString * (^lookupKeyForAssetFromPackageStub)
+    (NSString *, NSString *);
+@property(nonatomic, copy, nullable) NSObject<FlutterBinaryMessenger> * (^messengerStub)(void);
+@property(nonatomic, copy, nullable) void (^publishStub)(NSObject *);
+@property(nonatomic, copy, nullable) void (^registerViewFactoryStub)
+    (NSObject<FlutterPlatformViewFactory> *, NSString *);
+@property(nonatomic, copy, nullable) NSObject<FlutterTextureRegistry> * (^texturesStub)(void);
+@property(nonatomic, copy, nullable)
+    void (^registerViewFactoryWithGestureRecognizersBlockingPolicyStub)
+        (NSObject<FlutterPlatformViewFactory> *, NSString *,
+         FlutterPlatformViewGestureRecognizersBlockingPolicy);
+@end
+#endif
+
+@interface FlutterBinaryMessengerStub : NSObject <FlutterBinaryMessenger>
+
+// Stubs
+@property(nonatomic, copy, nullable) void (^cleanUpConnectionStub)(FlutterBinaryMessengerConnection)
+    ;
+@property(nonatomic, copy, nullable) void (^sendOnChannelMessageStub)(NSString *, NSData *);
+@property(nonatomic, copy, nullable) void (^sendOnChannelMessageBinaryReplyStub)
+    (NSString *, NSData *, FlutterBinaryReply);
+@property(nonatomic, copy, nullable)
+    FlutterBinaryMessengerConnection (^setMessageHandlerOnChannelBinaryMessageHandlerStub)
+        (NSString *, FlutterBinaryMessageHandler);
+@end
+
 NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m
index 8c8b297..6c7d246 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m
+++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m
@@ -3,6 +3,14 @@
 // found in the LICENSE file.
 
 #import "Stubs.h"
+#import <Foundation/Foundation.h>
+#import <StoreKit/StoreKit.h>
+
+#if TARGET_OS_OSX
+#import <FlutterMacOS/FlutterMacOS.h>
+#else
+#import <Flutter/Flutter.h>
+#endif
 
 @implementation SKProductSubscriptionPeriodStub
 
@@ -87,8 +95,8 @@
 
 @interface SKProductRequestStub ()
 
-@property(strong, nonatomic) NSSet *identifers;
-@property(strong, nonatomic) NSError *error;
+@property(nonatomic, strong) NSSet *identifers;
+@property(nonatomic, strong) NSError *error;
 
 @end
 
@@ -271,6 +279,14 @@
   return [[NSData alloc] initWithBase64EncodedString:originalString options:kNilOptions];
 }
 
+- (NSURL *)receiptURL {
+  if (self.returnNilURL) {
+    return nil;
+  } else {
+    return [[NSBundle mainBundle] appStoreReceiptURL];
+  }
+}
+
 @end
 
 @implementation SKReceiptRefreshRequestStub {
@@ -309,5 +325,325 @@
   }
   return self;
 }
+@end
+
+@implementation PaymentQueueStub
+
+@synthesize transactions;
+@synthesize delegate;
+
+- (void)finishTransaction:(SKPaymentTransaction *)transaction {
+  [self.observer paymentQueue:self.realQueue removedTransactions:@[ transaction ]];
+}
+
+- (void)addPayment:(SKPayment *_Nonnull)payment {
+  SKPaymentTransactionStub *transaction =
+      [[SKPaymentTransactionStub alloc] initWithState:self.testState payment:payment];
+  [self.observer paymentQueue:self.realQueue updatedTransactions:@[ transaction ]];
+}
+
+- (void)addTransactionObserver:(nonnull id<SKPaymentTransactionObserver>)observer {
+  self.observer = observer;
+}
+
+- (void)restoreCompletedTransactions {
+  [self.observer paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)self];
+}
+
+- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username {
+  [self.observer paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)self];
+}
+
+- (NSArray<SKPaymentTransaction *> *_Nonnull)getUnfinishedTransactions {
+  if (self.getUnfinishedTransactionsStub) {
+    return self.getUnfinishedTransactionsStub();
+  } else {
+    return @[];
+  }
+}
+
+#if TARGET_OS_IOS
+- (void)presentCodeRedemptionSheet {
+  if (self.presentCodeRedemptionSheetStub) {
+    self.presentCodeRedemptionSheetStub();
+  }
+}
+#endif
+
+#if TARGET_OS_IOS
+- (void)showPriceConsentIfNeeded {
+  if (self.showPriceConsentIfNeededStub) {
+    self.showPriceConsentIfNeededStub();
+  }
+}
+#endif
+
+- (void)restoreTransactions:(nullable NSString *)applicationName {
+  if (self.restoreTransactionsStub) {
+    self.restoreTransactionsStub(applicationName);
+  }
+}
+
+- (void)startObservingPaymentQueue {
+  if (self.startObservingPaymentQueueStub) {
+    self.startObservingPaymentQueueStub();
+  }
+}
+
+- (void)stopObservingPaymentQueue {
+  if (self.stopObservingPaymentQueueStub) {
+    self.stopObservingPaymentQueueStub();
+  }
+}
+
+- (void)removeTransactionObserver:(id<SKPaymentTransactionObserver>)observer {
+  self.observer = nil;
+}
+@end
+
+@implementation MethodChannelStub
+- (void)invokeMethod:(nonnull NSString *)method arguments:(id _Nullable)arguments {
+  if (self.invokeMethodChannelStub) {
+    self.invokeMethodChannelStub(method, arguments);
+  }
+}
+
+- (void)invokeMethod:(nonnull NSString *)method
+           arguments:(id _Nullable)arguments
+              result:(FlutterResult _Nullable)callback {
+  if (self.invokeMethodChannelWithResultsStub) {
+    self.invokeMethodChannelWithResultsStub(method, arguments, callback);
+  }
+}
 
 @end
+
+@implementation TransactionCacheStub
+- (void)addObjects:(nonnull NSArray *)objects forKey:(TransactionCacheKey)key {
+  if (self.addObjectsStub) {
+    self.addObjectsStub(objects, key);
+  }
+}
+
+- (void)clear {
+  if (self.clearStub) {
+    self.clearStub();
+  }
+}
+
+- (nonnull NSArray *)getObjectsForKey:(TransactionCacheKey)key {
+  if (self.getObjectsForKeyStub) {
+    return self.getObjectsForKeyStub(key);
+  }
+  return @[];
+}
+@end
+
+@implementation PaymentQueueHandlerStub
+
+@synthesize storefront;
+@synthesize delegate;
+
+- (void)paymentQueue:(nonnull SKPaymentQueue *)queue
+    updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
+  if (self.paymentQueueUpdatedTransactionsStub) {
+    self.paymentQueueUpdatedTransactionsStub(queue, transactions);
+  }
+}
+
+#if TARGET_OS_IOS
+- (void)showPriceConsentIfNeeded {
+  if (self.showPriceConsentIfNeededStub) {
+    self.showPriceConsentIfNeededStub();
+  }
+}
+#endif
+
+- (BOOL)addPayment:(nonnull SKPayment *)payment {
+  if (self.addPaymentStub) {
+    return self.addPaymentStub(payment);
+  } else {
+    return NO;
+  }
+}
+
+- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction {
+  if (self.finishTransactionStub) {
+    self.finishTransactionStub(transaction);
+  }
+}
+
+- (nonnull NSArray<SKPaymentTransaction *> *)getUnfinishedTransactions {
+  if (self.getUnfinishedTransactionsStub) {
+    return self.getUnfinishedTransactionsStub();
+  } else {
+    return @[];
+  }
+}
+
+- (nonnull instancetype)initWithQueue:(nonnull id<FLTPaymentQueueProtocol>)queue
+                     transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
+                      transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
+                restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
+    restoreCompletedTransactionsFinished:
+        (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished
+                   shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment
+                        updatedDownloads:(nullable UpdatedDownloads)updatedDownloads
+                        transactionCache:(nonnull id<FLTTransactionCacheProtocol>)transactionCache {
+  return [[PaymentQueueHandlerStub alloc] init];
+}
+
+#if TARGET_OS_IOS
+- (void)presentCodeRedemptionSheet {
+  if (self.presentCodeRedemptionSheetStub) {
+    self.presentCodeRedemptionSheetStub();
+  }
+}
+#endif
+
+- (void)restoreTransactions:(nullable NSString *)applicationName {
+  if (self.restoreTransactions) {
+    self.restoreTransactions(applicationName);
+  }
+}
+
+- (void)startObservingPaymentQueue {
+  if (self.startObservingPaymentQueueStub) {
+    self.startObservingPaymentQueueStub();
+  }
+}
+
+- (void)stopObservingPaymentQueue {
+  if (self.stopObservingPaymentQueueStub) {
+    self.stopObservingPaymentQueueStub();
+  }
+}
+
+- (nonnull instancetype)initWithQueue:(nonnull id<FLTPaymentQueueProtocol>)queue
+                     transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
+                      transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
+                restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed
+    restoreCompletedTransactionsFinished:
+        (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished
+                   shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment
+                        updatedDownloads:(nullable UpdatedDownloads)updatedDownloads {
+  return [[PaymentQueueHandlerStub alloc] init];
+}
+
+@end
+
+@implementation RequestHandlerStub
+
+- (void)startProductRequestWithCompletionHandler:(nonnull ProductRequestCompletion)completion {
+  if (self.startProductRequestWithCompletionHandlerStub) {
+    self.startProductRequestWithCompletionHandlerStub(completion);
+  }
+}
+@end
+
+/// This mock is only used in iOS tests
+#if TARGET_OS_IOS
+
+// This FlutterPluginRegistrar is a protocol, so to make a stub it has to be implemented.
+@implementation FlutterPluginRegistrarStub
+
+- (void)addApplicationDelegate:(nonnull NSObject<FlutterPlugin> *)delegate {
+  if (self.addApplicationDelegateStub) {
+    self.addApplicationDelegateStub(delegate);
+  }
+}
+
+- (void)addMethodCallDelegate:(nonnull NSObject<FlutterPlugin> *)delegate
+                      channel:(nonnull FlutterMethodChannel *)channel {
+  if (self.addMethodCallDelegateStub) {
+    self.addMethodCallDelegateStub(delegate, channel);
+  }
+}
+
+- (nonnull NSString *)lookupKeyForAsset:(nonnull NSString *)asset {
+  if (self.lookupKeyForAssetStub) {
+    return self.lookupKeyForAssetStub(asset);
+  }
+  return nil;
+}
+
+- (nonnull NSString *)lookupKeyForAsset:(nonnull NSString *)asset
+                            fromPackage:(nonnull NSString *)package {
+  if (self.lookupKeyForAssetFromPackageStub) {
+    return self.lookupKeyForAssetFromPackageStub(asset, package);
+  }
+  return nil;
+}
+
+- (nonnull NSObject<FlutterBinaryMessenger> *)messenger {
+  if (self.messengerStub) {
+    return self.messengerStub();
+  }
+  return [[FlutterBinaryMessengerStub alloc] init];  // Or default behavior
+}
+
+- (void)publish:(nonnull NSObject *)value {
+  if (self.publishStub) {
+    self.publishStub(value);
+  }
+}
+
+- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory> *)factory
+                     withId:(nonnull NSString *)factoryId {
+  if (self.registerViewFactoryStub) {
+    self.registerViewFactoryStub(factory, factoryId);
+  }
+}
+
+- (nonnull NSObject<FlutterTextureRegistry> *)textures {
+  if (self.texturesStub) {
+    return self.texturesStub();
+  }
+  return nil;
+}
+
+- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory> *)factory
+                              withId:(nonnull NSString *)factoryId
+    gestureRecognizersBlockingPolicy:
+        (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy {
+  if (self.registerViewFactoryWithGestureRecognizersBlockingPolicyStub) {
+    self.registerViewFactoryWithGestureRecognizersBlockingPolicyStub(
+        factory, factoryId, gestureRecognizersBlockingPolicy);
+  }
+}
+
+@end
+
+// This FlutterBinaryMessenger is a protocol, so to make a stub it has to be implemented.
+@implementation FlutterBinaryMessengerStub
+- (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
+  if (self.cleanUpConnectionStub) {
+    self.cleanUpConnectionStub(connection);
+  }
+}
+
+- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message {
+  if (self.sendOnChannelMessageStub) {
+    self.sendOnChannelMessageStub(channel, message);
+  }
+}
+
+- (void)sendOnChannel:(nonnull NSString *)channel
+              message:(NSData *_Nullable)message
+          binaryReply:(FlutterBinaryReply _Nullable)callback {
+  if (self.sendOnChannelMessageBinaryReplyStub) {
+    self.sendOnChannelMessageBinaryReplyStub(channel, message, callback);
+  }
+}
+
+- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel
+                                          binaryMessageHandler:
+                                              (FlutterBinaryMessageHandler _Nullable)handler {
+  if (self.setMessageHandlerOnChannelBinaryMessageHandlerStub) {
+    return self.setMessageHandlerOnChannelBinaryMessageHandlerStub(channel, handler);
+  }
+  return 0;
+}
+@end
+
+#endif
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml
index b960beb..bcb2807 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml
@@ -2,7 +2,7 @@
 description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
 repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 0.3.16
+version: 0.3.17
 
 environment:
   sdk: ^3.2.3