Co Author: Andy Pliszka
Cedar is an open source BDD testing framework from Pivotal Labs that makes test driven iPhone development quick and easy. The framework provides a large library of matchers so you can start testing right away on a large collection of objects. If you are familiar with RSpec or Jasmine you will immediately recognize the syntax for writing tests.
describe(@"this tutorial", ^{
it(@"makes setting up Cedar quick and easy", ^{
yourApp.isAwesome should be_truthy;
});
});
This is the first post in a series of blog articles that will teach you Cedar. The posts will walk you through test driven iPhone development to create a simple cooking app to save all your favorite recipes. You can download the completed code with Git commits for each major milestone so you can pick up at any point in the series.
The First Story
Displaying a list of recipes is the first piece of functionality any good recipe app should have. This feature can be described with the following user story:
As a user, I should be able to see a list of recipes, so I can cook a delicious dinner Scenario: User views the recipe list Given I have the recipe app installed on my iPhone When I open the recipe app Then I should see a list of my favorite recipes
Before we can test drive our first story we have to install Cedar.
Installing Cedar
Before you can install Cedar, make sure that the Apple OS X command line developer tools are installed on your machine. To install Cedar run the following command in your terminal:
$ curl -L https://raw.github.com/pivotal/cedar/master/install.sh | bash
This will download the code for the latest Cedar release to ~/.cedar
and install to ~/Library
. The script will also install Xcode templates and code snippets to make writing Cedar specs more convenient.
Setting Up Your Project
The first step in test driven iPhone development is to create a new iOS project in Xcode using the default Empty Application template. Then name the product Recipes
and set the class prefix to BDD
.
- From menu bar select
File
->New
->Project…
iOS
->Application
->Empty Application
- Product Name:
Recipes
- Class Prefix:
BDD
Add the Cedar Testing Target
Second, add a Cedar Testing Bundle to your project and name it Specs
. Then reference the app target by setting the test target to Recipes
. By using the bundle you can run tests just like OCUnit
, by pressing ⌘+U
.
- From menu bar select
File
->New
->Target...
iOS
->Cedar
->iOS Cedar Testing Bundle
- Product Name:
Specs
- Test Target:
Recipes
Add the Testing Scheme
The bundle will add the Specs
directory with all files needed to run the tests inside of Xcode and from the command line via rake Specs
. It will also add the ExampleSpec.mm
file which includes some sample Cedar tests. Before you can run the tests with ⌘+U
you will have to add the target to your test schemes.
- From menu bar select
Product
->Scheme
->Edit Scheme…
- Select the
Recipes
Scheme from the top - Select
Test
from the left - Click the
+
along the bottom - Select
Specs
thenAdd
Try running the example specs with ⌘+U
; you should see five passing tests in your console. These are sample specs to get you familiar with Cedar’s syntax and output. As noted in the file, you can find more information on the wiki.
Disable Auto-Hiding of the Console (optional)
If the console window closes before you have a chance to check it, disable auto-hiding of the console.
Preferences
->Behaviors
->Running
->Completes
- Uncheck
If no output, hide debugger
Writing Your First Test
First, we should test if our root view controller is an instance of UITableViewController
.
The easiest way to test drive this is to assert that app delegate’s root view controller is a UITableViewController
instance.
Add a new Cedar spec file to your project:
File
->New
->File...
iOS
->Cedar
->Cedar Spec
- Class to Spec:
BDDAppDelegate
- Check
Specs
"BDDAppDelegate.h"
using namespace Cedar::Matchers;
using namespace Cedar::Doubles;
SPEC_BEGIN(BDDAppDelegateSpec)
describe(@"BDDAppDelegate", ^{
__block BDDAppDelegate *model;
beforeEach(^{
});
});
SPEC_END
- Rename the subject under test to
delegate
- Initialize the app delegate in a
beforeEach
beforeEach
blocks get run before every block nested beneath it
- Add the
context
context
blocks encompass a scenario to set up for the tests to run under
- Call the delegate’s method as if it was called during a normal app launch
- Assert that the root view controller is an instance of `UITableViewController`
it
blocks are where your assertions belongbe_instance_of
is a Cedar matcher for asserting if an object is an instance of a particular class
#import "BDDAppDelegate.h"
using namespace Cedar::Matchers;
using namespace Cedar::Doubles;
SPEC_BEGIN(BDDAppDelegateSpec)
describe(@"BDDAppDelegate", ^{
__block BDDAppDelegate *delegate; // 1.
beforeEach(^{
delegate = [[[BDDAppDelegate alloc] init] autorelease]; // 2.
});
context(@"when the app is finished loading", ^{ // 3.
beforeEach(^{
[delegate application:nil didFinishLaunchingWithOptions:nil]; // 4.
});
it(@"should display a table view", ^{
delegate.window.rootViewController should be_instance_of([UITableViewController class]); // 5.
});
});
});
SPEC_END
Run the test suite; the console should print a failure.
The next step is to make the test pass. To do this we need to change BDDAppDelegate.m
‘s application:didFinishLaunchingWithOptions:
to:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// 1.
self.window.rootViewController = [[UITableViewController alloc] init];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
- Set the window’s root view controller to a new
UITableViewController
Re-run your tests; everything should pass. Congratulations! You’ve successfully test drove your first piece of iOS code.
Up Next
The next blog post in the Test Driven iPhone Development series will cover view controller testing. Here we will add some content to the UITableViewController
and actually see some data in the simulator.