如何使用 returns 数据的完成处理程序为方法编写单元测试?

How to write a unit test for a method with a completion handler that returns data?

我一直在尝试弄清楚如何使用 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];
}

您应该将 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];
    }
    

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