Cross-posted from the Prolific Interactive blog
A recent iOS project has familiarized Prolific with a new testing framework: Kiwi. Kiwi is described as a "Behavior Driven Development (BDD) library for iOS development" - meaning it provides a framework for testing expected code behavior. The purpose of defining behavior is to catch when it unintentionally changes during refactors or implementation of new features. Kiwi exists in an ecosystem of testing frameworks gaining popularity among iOS developers. There are plenty of great pieces out there exploring the pros and cons of each - and there is no definitive call on which is best to use. However, what is becoming more definitive is that iOS applications are not exempt from the notion that testing frameworks can help greatly in developing quality software.
If you're exploring or using tests in your iOS development and Kiwi is your framework of choice, read on as I explore one of its lesser documented features: the ability to use a block when stubbing methods.
The Assignment
You've been given the task of writing an application that displays blog posts associated with people. This is what the JSON you're expected to work with is going to look like:
{
"people" : [
{
"name" : "Jorge Luis Mendez",
"role" : "Senior iOS Engineer",
"blog_posts" : [
{
"title" : "Making Mantle Deserialization Generic",
"url" : "http://blog.prolificinteractive.com/2014/12/15/making-mantle-deserialization-generic/"
}
]
}
]
}
You've also heard that the server isn't up yet. But, that's okay - you know Kiwi can help you write code that behaves properly without that piece in place. Let's take a look at the start of this very application: http://github.com/prolificinteractive/kiwi-blocks-demo
Setup
The demo project has elements of how a typical Prolific application may start - a class that represents network interaction (PIDemoServer
), classes that represent the core application models (PIDemoPerson
, PIDemoBlogPost
), and a class that acts as a layer between application code and the details of retrieving JSON from the network and translating it into models
(PIDemoDataStore
). The key methods in PIDemoServer
and PIDemoDataStore
utilize completion
blocks to interact with their calling classes.
Given our focus on one specific Kiwi stub type, there are details missing from what a Prolific project might actually look like - most notably, a robust strategy for translating models (Prolific often uses Mantle). Also, given this application is supposed to be covered by tests, there should be test specs for how our server and model classes are to behave. However, the type of test we're interested in is in PIDemoDataStoreSpec
- let's take a look.
Kiwi's stub:withBlock:
in Action
Kiwi has pretty comprehensive documentation which includes a section on stubs. However, it doesn't yet include a mention of the stubbing method that will come in handy during our tests: stub:withBlock:
. Let's see it in action as we test PIDemoDataStore
's fetchPeopleWithCompletion:
method:
SPEC_BEGIN(PIDemoDataStoreSpec)
describe(@"PIDemoDataStore", ^{
describe(@"Class method: fetchPeopleWithCompletion:", ^{
typedef void (^PIDemoServerCallback)(id JSON, NSError *error);
context(@"Data fetched successfully", ^{
__block NSDictionary *json;
beforeEach(^{
json = @{
@"people" : @[
@{
@"name" : @"Jorge Luis Mendez",
@"role" : @"Senior iOS Engineer",
@"blog_posts" : @[
@{
@"title" : @"Making Mantle Deserialization Generic",
@"url" : @"http://blog.prolificinteractive.com/2014/12/15/making-mantle-deserialization-generic/"
}
]
},
@{
@"name" : @"Irene Duke",
@"role" : @"Senior Android Engineer",
@"blog_posts" : @[
@{
@"title" : @"A New Beginning",
@"url" : @"http://blog.prolificinteractive.com/2014/11/19/new-beginning/"
}
]
}
]
};
[PIDemoServer stub:@selector(GET:parameters:completion:)
withBlock:^id(NSArray *params) {
PIDemoServerCallback completion = params[2];
completion(json, nil);
return nil;
}];
});
it(@"Should deserialize into Person objects", ^{
__block PIDemoPerson *jorge;
__block PIDemoPerson *irene;
[PIDemoDataStore
fetchPeopleWithCompletion:^(NSArray *people, NSError *error) {
jorge = people[0];
irene = people[1];
}];
[[jorge.name shouldEventually] equal:@"Jorge Luis Mendez"];
[[jorge.role shouldEventualy] equal:@"Senior iOS Engineer"];
PIDemoBlogPost *jorgesBlogPost = jorge.blogPosts[0];
[[jorgesBlogPost.title shouldEventually] equal:@"Making Mantle Deserialization Generic"];
// ...etc...
});
});
});
});
SPEC_END
stub:withBlock:
call allows us to cleanly make a call to the completion block with canned information:
[PIDemoServer stub:@selector(GET:parameters:completion:)
withBlock:^id(NSArray *params) {
PIDemoServerCallback completion = params[2];
completion(json, nil);
return nil;
}];
__block NSError *serverError;
beforeEach(^{
serverError = [NSError errorWithDomain:@"test" code:100 userInfo:@{ @"user" : @"info" }];
[PIDemoServer stub:@selector(GET:parameters:completion:)
withBlock:^id(NSArray *params) {
PIDemoServerCallback completion = params[2];
completion(nil, serverError);
return nil;
}];
});
it(@"Should callback with error", ^{
__block NSError *errorReceived;
[PIDemoDataStore
fetchPeopleWithCompletion:^(NSArray *people, NSError *error) {
errorReceived = error;
}];
[[errorReceived shouldEventually] equal:serverError];
});
The Result
Awesome! Without the server being in place we're able to get to work with tests to verify our expectation of how the application should behave.
While it isn't written solely for stubbing server responses, this is where I've found it most helpful myself. If you're working on a project and using Kiwi, give stub:withBlock:
a try for testing methods that take in blocks.
This blog is licensed under Attribution-Noncommercial-Share Alike 3.0 Unported license.