基于 aryaxt 的 WebService 答案,这里有一个小技巧可以在不同的测试中获得不同的结果。
首先,在测试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
测试配置.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
然后你会定义你的“模拟”类别来定义模拟方法,例如:
#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
最后,在测试本身中,您将在 setup 中调整模拟方法,并在调用之前在每个测试中定义预期答案
我的服务测试.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.
}
备注:在我的示例中,TestConfiguration 使用类/选择器作为键而不是对象/选择器。这意味着类的每个对象都将为选择器使用相同的答案。这很可能是您的情况,因为 Web 服务通常是单例的。但它应该改进为一个对象/选择器,可能使用对象的内存地址而不是它的类