如何使用 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 结合使用来测试完成处理程序。这里有一个例子:
- 点击command+N创建XCTestCase子类:
为可测试实例和必要的模拟添加属性:
@interface SomeClassTests : XCTestCase
@property (nonatomic, strong) SomeClass *testableInstance;
@property (nonatomic, strong) NSURLSession *mockSession;
@property (nonatomic, strong) NSURLRequest *mockRequest;
@property (nonatomic, strong) NSHTTPURLResponse *mockResponse;
@end
设置属性:
- (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);
}
不要忘记在拆卸时清理:
- (void)tearDown
{
[(id)self.mockSession stopMocking];
self.mockResponse = nil;
self.mockRequest = nil;
self.mockSession = nil;
self.testableInstance = nil;
[super tearDown];
}
我们来测试一下出现错误的情况:
- (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];
}
所以我们将确保如果发生某些错误,那么完成处理程序将在参数处调用相同的错误。
当我们得到一些错误的状态代码时让我们测试一下:
- (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];
}
最后让我们在实际获取数据时进行测试:
- (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];
}
如果您打开覆盖范围,那么您会看到这样的测试完全覆盖了可测试代码:
我一直在尝试弄清楚如何使用 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 结合使用来测试完成处理程序。这里有一个例子:
- 点击command+N创建XCTestCase子类:
为可测试实例和必要的模拟添加属性:
@interface SomeClassTests : XCTestCase @property (nonatomic, strong) SomeClass *testableInstance; @property (nonatomic, strong) NSURLSession *mockSession; @property (nonatomic, strong) NSURLRequest *mockRequest; @property (nonatomic, strong) NSHTTPURLResponse *mockResponse; @end
设置属性:
- (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); }
不要忘记在拆卸时清理:
- (void)tearDown { [(id)self.mockSession stopMocking]; self.mockResponse = nil; self.mockRequest = nil; self.mockSession = nil; self.testableInstance = nil; [super tearDown]; }
我们来测试一下出现错误的情况:
- (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]; }
所以我们将确保如果发生某些错误,那么完成处理程序将在参数处调用相同的错误。
当我们得到一些错误的状态代码时让我们测试一下:
- (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]; }
最后让我们在实际获取数据时进行测试:
- (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]; }
如果您打开覆盖范围,那么您会看到这样的测试完全覆盖了可测试代码: