4

I'm trying to unit test our receipt verification server and while I can alter the internal API to avoid this problem, it means we're not fully testing the client API so I'd like to avoid that.

As part of our API, we pass through a SKPaymentTransaction and then pass the Transaction.transactionReceipt through to our server.

To test this correctly, I'd like to create an instance of SKPaymentTransaction with a transactionReceipt of my choosing (valid and invalid values).

Unfortunately, SKPaymentTransaction defines the transactionReceipt property as read only, and I cannot declare an extension/sub-class defining it as readwrite due to this.

I also don't seem to be able to cast the SKPaymentTransaction pointer to a char* to manually inject the values in memory as Xcode won't allow this under ARC.

Does anyone have an idea of how I can achieve what I'm looking for?

Thanks Lee

Community
  • 1
  • 1
Lee Winder
  • 857
  • 12
  • 35

2 Answers2

3

Turns out I can swizzle the transactionReceipt getter to inject my own data into the call.

So I end up with something like

-(void)test_function
{
    SKPaymentTransaction* invalidTransaction = [[SKPaymentTransaction alloc] init];

    Method swizzledMethod = class_getInstanceMethod([self class], @selector(replaced_getTransactionReceipt));
    Method originalMethod = class_getInstanceMethod([invalidTransaction class], @selector(transactionReceipt));

    method_exchangeImplementations(originalMethod, swizzledMethod);

    // Call to receipt verification server
}

- (NSData*)replaced_getTransactionReceipt
{
    return [@"blah" dataUsingEncoding:NSUTF8StringEncoding];
}

I wrote a blog post showing my process, and giving a bit more detail here.
http://engineering-game-dev.com/2014/07/23/injecting-data-into-obj-c-readonly-properties/

Lee Winder
  • 857
  • 12
  • 35
0

I subclassed SKPaymentTransaction (e.g. MutableSKPaymentTransaction), overriding the readonly parameters. There is already a mutable SKPaymentTransaction, which you can use, or you can override SKPayment in a similar way.

Example:

in the header file (MutableSKPaymentTransaction.h) file

#import <StoreKit/StoreKit.h>

@interface MutableSKPaymentTransaction : SKPaymentTransaction

@property (readwrite, copy, nonatomic) NSError * error;
@property (readwrite, copy, nonatomic) SKPayment * payment;
@property (readwrite, copy, nonatomic) NSString * transactionIdentifier;
@property (readwrite, copy, nonatomic) NSDate * transactionDate;
@property (readwrite, copy, nonatomic) NSArray * downloads;
@property (readwrite, copy, nonatomic) SKPaymentTransaction *originalTransaction;
@property (assign, nonatomic) SKPaymentTransactionState transactionState;

@end

and in the method file (MutableSKPaymentTransaction.m):

#import "MutableSKPaymentTransaction.h"

@implementation MutableSKPaymentTransaction

// readonly override
@synthesize error = _error;
@synthesize payment = _payment;
@synthesize transactionIdentifier = _transactionIdentifier;
@synthesize transactionDate = _transactionDate;
@synthesize downloads = _downloads;
@synthesize originalTransaction = _originalTransaction;
@synthesize transactionState = _transactionState;

@end