Построение поверх ответа WebService от aryaxt, вот небольшая хитрость, чтобы иметь возможность получать разные результаты в разных тестах.
Во-первых, вам нужен одноэлементный объект, который будет использоваться для хранения желаемого ответа, прямо перед тестом 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
Тогда вы определите свою категорию "Mock"для определения фиктивных методов, таких как:
#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
И, наконец, в самих тестах вы должны использовать метод макета в настройке и определять ожидаемый ответ в каждом тесте перед вызовом
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.
}
Примечание: в моем примере TestConfiguration использует класс / селектор в качестве ключа вместо объекта / селектора.Это означает, что каждый объект класса будет использовать один и тот же ответ для селектора.Это, скорее всего, ваш случай, так как веб-сервисы часто бывают одноразовыми.Но это должно быть улучшено, чтобы объект / селектор мог использовать адрес памяти объекта вместо его класса