bdd cedar ios labs tdd

Test Driven iPhone Development with Cedar

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

Cedar Completed Installation

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.

  1. From menu bar select File -> New -> Project…
  2. iOS -> Application -> Empty Application
  3. Product Name: Recipes
  4. Class Prefix: BDD

Create a New iOS Project

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.

  1. From menu bar select File -> New -> Target...
  2. iOS -> Cedar -> iOS Cedar Testing Bundle
  3. Product Name: Specs
  4. Test Target: Recipes

Add the iOS Cedar Testing Bundle

Configure the Target

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.

  1. From menu bar select Product -> Scheme -> Edit Scheme…
  2. Select the Recipes Scheme from the top
  3. Select Test from the left
  4. Click the + along the bottom
  5. Select Specs then Add

Add the Spec Scheme 1 of 3

Add the Spec Scheme 2 of 3

Add the Spec Scheme 3 of 3

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.

Cedar Example Specs Passing

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.

  1. Preferences -> Behaviors -> Running -> Completes
  2. 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:

  1. File -> New -> File...
  2. iOS -> Cedar -> Cedar Spec
  3. Class to Spec: BDDAppDelegate
  4. Check Specs
"BDDAppDelegate.h"

using namespace Cedar::Matchers;
using namespace Cedar::Doubles;

SPEC_BEGIN(BDDAppDelegateSpec)

describe(@"BDDAppDelegate", ^{
    __block BDDAppDelegate *model;

    beforeEach(^{

    });
});

SPEC_END
  1. Rename the subject under test to delegate
  2. Initialize the app delegate in a beforeEach
    • beforeEach blocks get run before every block nested beneath it
  3. Add the context
    • context blocks encompass a scenario to set up for the tests to run under
  4. Call the delegate’s method as if it was called during a normal app launch
  5. Assert that the root view controller is an instance of `UITableViewController`
    • it blocks are where your assertions belong
    • be_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.

First Failing Spec

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;
}
  1. 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.