I want to stub a method which takes a block as a parameter using Kiwi. Here is the full explanation with code:
I have a class named TestedClass
which has a method testedMethod
which dependent on class NetworkClass
which calls via AFNetworking to a server, and return its response via block. Translating to code:
@interface TestedClass : NSObject
-(void)testMethod;
@end
-(void)testMethod
{
NetworkClass *networkClass = [[NetworkClass alloc] init];
[networkClass networkMethod:^(id result)
{
// code that I want to test according to the block given which I want to stub
...
}];
}
typedef void (^NetworkClassCallback)(id result);
@interface NetworkClass : NSObject
-(void)networkMethod:(NetworkClassCallback)handler;
@end
-(void) networkMethod:(NetworkClassCallback)handler
{
NSDictionary *params = @{@"param":@", @"value"};
NSString *requestURL = [NSString stringWithFormat:@"www.someserver.com"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURLURLWithString:requestURL]];
NSURLRequest *request = [httpClient requestWithMethod:@"GET" path:requestURL parameters:params];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
handler(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
handler(nil);
}];
[operation start];
}
How can I use Kiwi to stub networkMethod
with block in order to unit test testMethod
?
UPDATE: Found how to do this in Kiwi, see my answer below.
Here is how you do this in Kiwi:
First, you must dependency inject NetworkClass
to TestedClass
(if it's not clear how, please add a comment and I'll explain; this can be done as a property for simplicity. This is so that you can operate on a mock object for the NetworkClass
)
Then your spec, create the mock for the network class and create your class that you want to unit test:
SPEC_BEGIN(TestSpec)
describe(@"describe goes here", ^{
it(@"should test block", ^{
NetworkClass *mockNetworkClass = [NetworkClass mock];
KWCaptureSpy *spy = [mockNetworkClass captureArgument:@selector(networkMethod:) atIndex:0];
TestedClass testClass = [TestedClass alloc] init];
testClass.networkClass = mockNetworkClass;
[testClass testMethod];
NetworkClassCallback blockToRun = spy.argument;
blockToRun(nil);
// add expectations here
});
});
SPEC_END
To explain what's going on here:
You are creating TestedClass
and calling testMethod
. However, before that, we are creating something called Spy
- its job is to capture the block in the first parameter when networkMethod:
is called. Now, it's time to actually execute the block itself.
It's easy to be confused here so I'll emphasize this: the order of calls is important; you first declare the spy, then call the tested method, and only then you're actually calling and executing the block!
This will give you the ability to check what you want as you're the one executing the block.
Hope it helps for other, as it took me quite sometime to understand this flow.