iOS技术点资源整理

iOS网络请求模拟库OHHTTPStubs的介绍和使用

2019-04-26  本文已影响14人  黄梦轩

你能使用OHHTTPStubs做什么

OHHTTPStubs的主要功能有两点:

  1. 伪造网络请求返回的数据
  2. 模拟网络请求时的慢网环境

我们通常会在以下情况下使用:

  1. 伪造数据、模拟慢网环境,检测APP在网络环境不好的情况下的行为。
  2. 单元测试时,使用OHHTTPStubs提供模拟数据。
  3. 在开发阶段,模拟网络请求数据,不依赖服务器而进行本地开发,通过这样的方式来加速开发。

基本用法

  1. 拦截域名为mywebservice.com的http请求,并返回一个数组对象。
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
    return [request.URL.host isEqualToString:@"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
    NSArray *array = @[@"Hello", @"world"];
    return [OHHTTPStubsResponse responseWithJSONObject:array statusCode:200 headers:nil];
}];
  1. mywebservice.com发送GET请求,这个时候会收到成功的返回,并且responseObject为数组[@"Hello", @"world"]
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"http://mywebservice.com" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable
responseObject) {
    NSArray *data = responseObject;
    NSLog(@"%@", data);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"%@", error);
}];

可以看到,OHHTTPStubs的使用非常简单方便,设置需要拦截的URL,提供拦截到请求之后,提供需要返回的OHHTTPStubsResponse对象。

OHHTTPStubs主要接口

  1. 添加stub
+(id<OHHTTPStubsDescriptor>)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock
                                   withStubResponse:(OHHTTPStubsResponseBlock)responseBlock;

这是OHHTTPStubs的类方法,我们通过调用这个方法来对URL进行拦截。
在参数testBlock中进行URL的匹配,如果返回YES,则该请求将会被拦截。
在参数responseBlock中,返回拦截了该请求之后的响应数据OHHTTPStubsResponse对象。

  1. OHHTTPStubsResponse对象

OHHTTPStubsResponse对象是作为被拦截的请求的响应,它包含HTTP响应头、响应正文、状态码、响应时间等信息。

OHHTTPStubsResponse这个类提供了快捷方法来创建该对象。

+(instancetype)responseWithData:(NSData*)data
                     statusCode:(int)statusCode
                        headers:(nullable NSDictionary*)httpHeaders;
+(instancetype)responseWithFileAtPath:(NSString *)filePath
                           statusCode:(int)statusCode
                              headers:(nullable NSDictionary*)httpHeaders;

+(instancetype)responseWithFileURL:(NSURL *)fileURL
                        statusCode:(int)statusCode
                           headers:(nullable NSDictionary *)httpHeaders;
+ (instancetype)responseWithJSONObject:(id)jsonObject
                            statusCode:(int)statusCode
                               headers:(nullable NSDictionary *)httpHeaders;
+(instancetype)responseWithError:(NSError*)error;
  1. 网络状况的模拟

网络状况的模拟主要是通过OHHTTPStubsResponse对象的requestTimeresponseTime两个属性来控制。

对于responseTime,OHHTTPStubs已经定义了一些常量值供我们使用

const double OHHTTPStubsDownloadSpeed1KBPS  =-     8 / 8; // kbps -> KB/s
const double OHHTTPStubsDownloadSpeedSLOW   =-    12 / 8; // kbps -> KB/s
const double OHHTTPStubsDownloadSpeedGPRS   =-    56 / 8; // kbps -> KB/s
const double OHHTTPStubsDownloadSpeedEDGE   =-   128 / 8; // kbps -> KB/s
const double OHHTTPStubsDownloadSpeed3G     =-  3200 / 8; // kbps -> KB/s
const double OHHTTPStubsDownloadSpeed3GPlus =-  7200 / 8; // kbps -> KB/s
const double OHHTTPStubsDownloadSpeedWifi   =- 12000 / 8; // kbps -> KB/s

可以通过如下代码设置requestTimeresponseTime

[[OHHTTPStubsResponse responseWithData:data statusCode:200 headers:@{@"Content-Type":@"application/json"}]
                requestTime:1.0f responseTime:OHHTTPStubsDownloadSpeed3G];
  1. 移除注册的stub

查看stubRequestsPassingTest:withStubResponse:的源码

+(id<OHHTTPStubsDescriptor>)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock
                                   withStubResponse:(OHHTTPStubsResponseBlock)responseBlock
{
    OHHTTPStubsDescriptor* stub = [OHHTTPStubsDescriptor stubDescriptorWithTestBlock:testBlock
                                                                       responseBlock:responseBlock];
    [OHHTTPStubs.sharedInstance addStub:stub];
    return stub;
}

+ (instancetype)sharedInstance
{
    static OHHTTPStubs *sharedInstance = nil;

    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
}

我们可以看到,每次添加一个stub之后,会创建一个OHHTTPStubsDescriptor实例由单例对象OHHTTPStubs持有。如果我们不手动将添加的stub移除,势必会造成内存常驻。我们可以调用以下方法移除添加的stub。

+(BOOL)removeStub:(id<OHHTTPStubsDescriptor>)stubDesc;
+(void)removeAllStubs;
  1. 启用和禁用stubs

所有的stubs默认是启用的,即默认会对需要拦截的请求进行拦截。可以通过以下方法启用或禁用拦截:

+(void)setEnabled:(BOOL)enabled;
+ (void)setEnabled:(BOOL)enabled forSessionConfiguration:(NSURLSessionConfiguration *)sessionConfig;
  1. 添加监听事件
/**
 添加一个在每个stub被触发都执行一次的block

 @param block 每个stub被触发都执行一次的block
 */
+(void)onStubActivation:( nullable void(^)(NSURLRequest* request, id<OHHTTPStubsDescriptor> stub, OHHTTPStubsResponse* responseStub) )block;

/**
 添加一个在OHHTTPStubs发生请求重定向的时候被执行的block

 @param block 在OHHTTPStubs发生请求重定向的时候被执行的block
 */
+(void)onStubRedirectResponse:( nullable void(^)(NSURLRequest* request, NSURLRequest* redirectRequest, id<OHHTTPStubsDescriptor> stub, OHHTTPStubsResponse* responseStub) )block;

/**
 添加一个在stub完成之后会被执行的block

 @param block 在stub完成之后会被执行的block
 */
+(void)afterStubFinish:( nullable void(^)(NSURLRequest* request, id<OHHTTPStubsDescriptor> stub, OHHTTPStubsResponse* responseStub, NSError *error) )block;

/**
 添加一个在OHHTTPStubs遇到无法拦截的请求时调用的block

 @param block 在OHHTTPStubs遇到无法拦截的请求时调用的block
 */
+(void)onStubMissing:( nullable void(^)(NSURLRequest* request) )block;

  1. POST请求时的HTTPBody

当使用NSURLSession发送POST请求时,请求的body会在到达OHHTTPStubs时被置为nil。也就是说在testBlockresponseBlock中,直接获取NSURLRequestHTTPBody会得到nil

OHHTTPStubs通过方法置换在调用NSURLRequestsetHTTPBody:时,将HTTPBody的内容做了一个备份。提供了方法OHHTTPStubs_HTTPBody来获取备份的HTTPBody

具体代码如下:

NSString * const OHHTTPStubs_HTTPBodyKey = @"HTTPBody";

@implementation NSURLRequest (HTTPBodyTesting)

- (NSData*)OHHTTPStubs_HTTPBody
{
    return [NSURLProtocol propertyForKey:OHHTTPStubs_HTTPBodyKey inRequest:self];
}

@end

#pragma mark - NSMutableURLRequest+HTTPBodyTesting

typedef void(*OHHHTTPStubsSetterIMP)(id, SEL, id);
static OHHHTTPStubsSetterIMP orig_setHTTPBody;

static void OHHTTPStubs_setHTTPBody(id self, SEL _cmd, NSData* HTTPBody)
{
    // store the http body via NSURLProtocol
    if (HTTPBody) {
        [NSURLProtocol setProperty:HTTPBody forKey:OHHTTPStubs_HTTPBodyKey inRequest:self];
    } else {
        // unfortunately resetting does not work properly as the NSURLSession also uses this to reset the property
    }

    orig_setHTTPBody(self, _cmd, HTTPBody);
}

/**
 *   Swizzles setHTTPBody: in order to maintain a copy of the http body for later
 *   reference and calls the original implementation.
 *
 *   @warning Should not be used in production, testing only.
 */
@interface NSMutableURLRequest (HTTPBodyTesting) @end

@implementation NSMutableURLRequest (HTTPBodyTesting)

+ (void)load
{
    orig_setHTTPBody = (OHHHTTPStubsSetterIMP)OHHTTPStubsReplaceMethod(@selector(setHTTPBody:),
                                                                       (IMP)OHHTTPStubs_setHTTPBody,
                                                                       [NSMutableURLRequest class],
                                                                       NO);
}

@end

注意事项

提交到App Store

OHHTTPStubs中没有使用任何私有的API,它是可以被提交到App Store的。但是我们基本上只会在开发阶段使用stubs,所以是没有必要把stubs提交到App Store的。

我们可以通过#if DEBUG块的方式来避免提交相关代码到App Store。

为了避免在发布前忘记移除对OHHTTPStubs的导入,可以使用下面的方式对pod进行配置:

pod 'OHHTTPStubs', :configurations => ['Debug', 'Development']

或以下代码:

pod 'OHHTTPStubs', :configurations => 'Debug'

如果配置为仅在Debug模式下导入OHHTTPStubs,那么在其它模式下使用OHHTTPStubs就会报错。所以一定要记得使用#if DEBUG块或者删除相关的代码。

最后附上github地址:https://github.com/AliSoftware/OHHTTPStubs

上一篇下一篇

猜你喜欢

热点阅读