【问题标题】:How to write a unit test for a method with a completion handler that returns data?如何为具有返回数据的完成处理程序的方法编写单元测试?
【发布时间】:2019-05-19 12:58:49
【问题描述】:

我一直在试图弄清楚如何使用 OCMock 为这种方法编写单元测试。有人可以帮我吗?

- (void)executeRequest:(NSURLRequest *)request withCompletionHandler:(void (^)(id responseData, NSError *error))completionHandler
{
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error)
        {
            NSLog(@"Network error occurred: %@", [error localizedDescription]);
            dispatch_async(dispatch_get_main_queue(), ^(void) { completionHandler(nil, error);
            });
            return;
        }
    if ([response isKindOfClass:NSHTTPURLResponse.class])
    {
        NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
        if (statusCode != 200)
        {
            NSLog(@"Network error occurred. Status code: %ld", (long)statusCode);
            return;
        }
    }
        completionHandler(data, nil);
    }];
    [dataTask resume];
}

【问题讨论】:

  • 也许将测试(断言方法)添加到完成处理程序?例如,设置测试以使请求失败,然后测试是否使用 (nil, error) 调用完成处理程序
  • 也许你可以写一些代码示例?

标签: ios objective-c unit-testing nsurlsession nsurlrequest


【解决方案1】:

您应该将 OCMArg 与 checkWithBlock 或 invokeBlockWithArgs 一起使用来测试完成处理程序。这里举个例子:

  1. 点击command+N创建XCTestCase子类:

  1. 为可测试的实例和必要的模拟添加属性:

    @interface SomeClassTests : XCTestCase
    
    @property (nonatomic, strong) SomeClass *testableInstance;
    @property (nonatomic, strong) NSURLSession *mockSession;
    @property (nonatomic, strong) NSURLRequest *mockRequest;
    @property (nonatomic, strong) NSHTTPURLResponse *mockResponse;
    
    @end 
    
  2. 设置属性:

    - (void)setUp
    {
        [super setUp];
        self.testableInstance = [SomeClass new];
        self.mockSession = OCMClassMock([NSURLSession class]);
        self.mockRequest = OCMClassMock([NSURLRequest class]);
        self.mockResponse = OCMClassMock([NSHTTPURLResponse class]);
        OCMStub(ClassMethod([(id)self.mockSession sharedSession])).andReturn(self.mockSession);
    }
    
  3. 拆机时别忘了清理:

    - (void)tearDown
    {
        [(id)self.mockSession stopMocking];
        self.mockResponse = nil;
        self.mockRequest = nil;
        self.mockSession = nil;
        self.testableInstance = nil;
        [super tearDown];
    }
    
  4. 我们来测试一下发生错误的情况:

    - (void)testWhenErrorOccuersThenCompletionWithSameError
    {
        // arrange
        NSError *givenError = [[NSError alloc] initWithDomain:@"Domain" code:0 userInfo:nil];
        OCMStub([self.mockSession dataTaskWithRequest:[OCMArg any] completionHandler:([OCMArg invokeBlockWithArgs:@"", self.mockResponse, givenError, nil])]);
    
        void (^givenCompletion)(id  _Nonnull, NSError * _Nonnull) = ^void(id  _Nonnull responseData, NSError * _Nonnull resultError) {
            // assert
            XCTAssertNil(responseData);
            XCTAssertEqual(resultError, givenError);
        };
    
        // act
        [self.testableInstance executeRequest:self.mockRequest withCompletionHandler:givenCompletion];
    }
    

所以我们将确保如果发生一些错误,那么完成处理程序将在参数处调用相同的错误。

  1. 当我们得到一些错误的状态码时,让我们测试一下:

    - (void)testWhenBadStatusCodeThenReturnWithoutCompletion
    {
        // arrange
        OCMStub([self.mockResponse statusCode]).andReturn(403);
        OCMStub([self.mockSession dataTaskWithRequest:[OCMArg any] completionHandler:([OCMArg checkWithBlock:^BOOL(id param) {
            void (^passedCompletion)(NSData *data, NSURLResponse *response, NSError *error) = param;
            passedCompletion(nil, self.mockResponse, nil);
            return YES;
        }])]);
    
        void (^givenCompletion)(id  _Nonnull, NSError * _Nonnull) = ^void(id  _Nonnull responseData, NSError * _Nonnull resultError) {
            // assert
            XCTFail("Shouldn't be reached");
        };
    
        // act
        [self.testableInstance executeRequest:self.mockRequest withCompletionHandler:givenCompletion];
    }
    
  2. 最后让我们测试一下我们什么时候真正得到数据:

    - (void)testWhenSuccesThenCompletionWithSameData
    {
        // arrange
        NSData *givenData = [NSData data];
        OCMStub([self.mockResponse statusCode]).andReturn(200);
        OCMStub([self.mockSession dataTaskWithRequest:[OCMArg any] completionHandler:([OCMArg checkWithBlock:^BOOL(id param) {
            void (^passedCompletion)(NSData *data, NSURLResponse *response, NSError *error) = param;
            passedCompletion(givenData, self.mockResponse, nil);
            return YES;
        }])]);
    
        void (^givenCompletion)(id  _Nonnull, NSError * _Nonnull) = ^void(id  _Nonnull responseData, NSError * _Nonnull resultError) {
            // assert
            XCTAssertEqual(responseData, givenData);
        };
    
        // act
        [self.testableInstance executeRequest:self.mockRequest withCompletionHandler:givenCompletion];
    }
    

如果你打开一个覆盖,那么你会看到这样的测试完全覆盖了可测试的代码:

【讨论】:

    猜你喜欢
    • 2021-10-17
    • 2017-11-13
    • 1970-01-01
    • 2015-05-30
    • 2011-03-28
    • 2018-02-25
    • 1970-01-01
    • 2019-12-16
    • 2011-12-04
    相关资源
    最近更新 更多