I'm stuck trying to figure out how to write a unit test for such a method using OCMock. Can someone help me with it?
- (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];
}
You should use OCMArg with checkWithBlock or invokeBlockWithArgs to test completions handlers. Here an example:
Add properties for testable instance and necessary mocks:
@interface SomeClassTests : XCTestCase
@property (nonatomic, strong) SomeClass *testableInstance;
@property (nonatomic, strong) NSURLSession *mockSession;
@property (nonatomic, strong) NSURLRequest *mockRequest;
@property (nonatomic, strong) NSHTTPURLResponse *mockResponse;
@end
Setup properties:
- (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);
}
Don't forget to clean up at tear down:
- (void)tearDown
{
[(id)self.mockSession stopMocking];
self.mockResponse = nil;
self.mockRequest = nil;
self.mockSession = nil;
self.testableInstance = nil;
[super tearDown];
}
Let's test the case when an error is occurs:
- (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];
}
So we will sure that if some error occurs then the completion handler will invokes with same error at the argument.
Let's test when we get some bad status code:
- (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];
}
And finally lets test when we actually get data:
- (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];
}
If you will switch on a coverage then you will see that such test fully cover testable code: