美文网首页iOS技术点资源整理
iOS网络请求模拟库OHHTTPStubs的介绍和使用

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

作者: 黄梦轩 | 来源:发表于2019-04-26 16:11 被阅读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这个类提供了快捷方法来创建该对象。

    • 使用NSData作为响应数据:
    +(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;
    
    • 使用JSON对象来作为响应数据:
    + (instancetype)responseWithJSONObject:(id)jsonObject
                                statusCode:(int)statusCode
                                   headers:(nullable NSDictionary *)httpHeaders;
    
    • 直接返回一个网络错误:
    +(instancetype)responseWithError:(NSError*)error;
    
    1. 网络状况的模拟

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

    • requestTime
      请求在发送前必须等待的时间
    • responseTime
      正数时,responseTime就是从发送请求到接收到完整响应数据的时间。
      负数时,responseTime就是下载速度,会根据需要下载的数据的大小,动态的计算下载完成所需要的时间。

    对于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
    

    注意事项

    • OHHTTPStubs不能用于后台会话(由[NSURLSessionConfiguration backgroundSessionConfiguration]创建的会话),因为后台会话由iOS系统自己维护,并且不允许使用NSURLProtocols
    • OHHTTPStubs不能模拟数据上传。NSURLProtocolClient协议并没有提供任何方式通知delegate数据已经发送,所以一个NSURLRequestHTTPBodyHTTPBodyStream或是由-[NSURLSession uploadTaskWithRequest:fromData:]提供的数据都会被忽略。最重要的是使用OHHTTPStubs来stub一个请求后,代理方法-URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:永远也不会被调用。
    • OHHTTPStubs还有一个重定向的问题,具有零延迟的重定向会以一个空响应结束。

    提交到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

    相关文章

      网友评论

        本文标题:iOS网络请求模拟库OHHTTPStubs的介绍和使用

        本文链接:https://www.haomeiwen.com/subject/bauzgqtx.html