Basandosi sulla risposta WebService di aryaxt, ecco un piccolo trucco per essere in grado di ottenere risultati diversi in test diversi.
In primo luogo, è necessario un oggetto Singleton che verrà utilizzata per memorizzare la risposta desiderata, proprio prima della prova TestConfiguration.h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
void MethodSwizzle(Class c, SEL orig, SEL new);
@interface TestConfiguration : NSObject
@property(nonatomic,strong) NSMutableDictionary *results;
+ (TestConfiguration *)sharedInstance;
-(void)setNextResult:(NSObject *)result
forCallToObject:(NSObject *)object
selector:(SEL)selector;
-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector;
@end
TestConfiguration.m
#import "TestConfiguration.h"
void MethodSwizzle(Class c, SEL orig, SEL new) {
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
};
@implementation TestConfiguration
- (id)init
{
self = [super init];
if (self) {
self.results = [[NSMutableDictionary alloc] init];
}
return self;
}
+ (TestConfiguration *)sharedInstance
{
static TestConfiguration *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[TestConfiguration alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
-(void)setNextResult:(NSObject *)result
forCallToObject:(NSObject *)object
selector:(SEL)selector
{
NSString *className = NSStringFromClass([object class]);
NSString *selectorName = NSStringFromSelector(selector);
[self.results setObject:result
forKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]];
}
-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector
{
NSString *className = NSStringFromClass([object class]);
NSString *selectorName = NSStringFromSelector(selector);
return [self.results objectForKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]];
}
@end
Quindi definisci la tua categoria "Mock" per definire i metodi di simulazione, come ad esempio:
#import "MyWebService+Mock.h"
#import "TestConfiguration.h"
@implementation MyWebService (Mock)
-(void)mockFetchEntityWithId:(NSNumber *)entityId
success:(void (^)(Entity *entity))success
failure:(void (^)(NSError *error))failure
{
Entity *response = (Entity *)[[TestConfiguration sharedInstance] getResultForCallToObject:self selector:@selector(fetchEntityWithId:success:failure:)];
if (response == nil)
{
failure([NSError errorWithDomain:@"entity not found" code:1 userInfo:nil]);
}
else{
success(response);
}
}
@end
E, infine, nelle prove stesse, si sarebbe swizzle il metodo finto nel setup, e definire la risposta attesa in ciascuna prova, prima della chiamata
MyServiceTest.m
- (void)setUp
{
[super setUp];
//swizzle webservice method call to mock object call
MethodSwizzle([MyWebService class], @selector(fetchEntityWithId:success:failure:), @selector(mockFetchEntityWithId:success:failure:));
}
- (void)testWSMockedEntity
{
/* mock an entity response from the server */
[[TestConfiguration sharedInstance] setNextResult:[Entity entityWithId:1]
forCallToObject:[MyWebService sharedInstance]
selector:@selector(fetchEntityWithId:success:failure:)];
// now perform the call. You should be able to call STAssert in the blocks directly, since the success/error block should now be called completely synchronously.
}
Osservazioni : nel mio esempio, TestConfiguration utilizza la classe/selettore come chiave anziché oggetto/selettore. Ciò significa che ogni oggetto della classe utilizzerà la stessa risposta per il selettore. Questo è molto probabilmente il tuo caso, dato che i servizi web sono spesso singleton. Ma dovrebbe essere migliorato per un oggetto/selettore magari usando l'indirizzo di memoria dell'oggetto invece della sua classe
Non potreste avere un oggetto singleton come "TestConfiguration" dove dovreste raccogliere i risultati falsi desiderati per ogni chiamata, da nella tua categoria Mock? –
Puoi esporre qualsiasi interno come proprietà, quindi puoi configurare la tua classe come desideri. –