Starter Step

Archive for the ‘Testing’ Category

I have just added simple scripting support to UISpec. It allows you to send a String as a script and have it run dynamically. This really opens up the possibility of using UISpec with other languages like Ruby, Java, etc. The following is taken from the documentation found here.

UIScript’s goal is to provide a simple lightweight scripting language that supports all the functionality of UISpec, but without having to write Objective-C code. Instead you write UIScript which is very similar to Smalltalk.

Currently UIScript only supports straight messages, which means there is no support for things like variables and loops or decisions (for, while, if else, etc…). And right now UISpec recognizes only Strings, Integers, and BOOLs.

You can run a script in your test by calling the C function:

UIQuery * $(NSString *script, …);


Like in Objective-C, UIScript is based on sending messages to objects. The only real difference in writing UIScript vs Objective-C is that you take out all the dot notation and brackets.

$(@”navigationButton touch”);
$(@”tableView tableViewCell all should not be selected”);

As you can see it’s very similar to what you are used to writing, except without all the syntactic sugar, making the script even more readable.


Strings in UISpec are just quoted values like ‘Hello’ or ‘Good Bye’, and there is no need to use the @ symbol as a prefix like you do in Objective-C.

$(@”navigationButton label text:’Save’ touch”);

$(@”textField placeholder:’Username’ setText:’bkuser'”);
//If you need to include a String dynamically just use NSString formatting
$(@”textField placeholder:’Username’ setText:’%@'”, [Config userName]);


Currently Integers are the only number support in UIScript. We hope to have support for decimals very soon.

$(@”tableView should have numberOfSections:3″);

$(@”label text:’Returns’ parent tableViewCell should have accessoryType:3″);
//Or you can use NSString formatting
$(@”label text:’Returns’ parent tableViewCell should have accessoryType:%d”, UITableViewCellAccessoryCheckmark);


The BOOLs YES and NO are also supported in UIScript.

$(@”tableView tableViewCell first should have isSelected:YES”);
$(@”tableView tableViewCell last should have isSelected:NO”);

//Or you can use NSString formatting
int rows = $(@”tableView numberOfRowsInSection:0″);
$(@”tableView should have:%@”, rows==3);


Here is the itShouldUpdateUserRoles example from the demo rewritten using UIScript

-(void)itShouldUpdateUserRoles {
[self addTestUser];

$(@”label with text:’Brian Knorr’ touch”);
$(@”label text:’User Roles’ touch”);

$(@”tableView scrollToBottom”);
$(@”label text:’Returns’ touch”);
$(@”label text:’Returns’ parent tableViewCell should be selected”);
$(@”label text:’Returns’ parent tableViewCell should have accessoryType:%d”, UITableViewCellAccessoryCheckmark);

$(@”label text:’Returns’ touch”);
$(@”label text:’Returns’ parent tableViewCell should have accessoryType:%d”, UITableViewCellAccessoryNone);

$(@”view:’UINavigationItemButtonView’ touch”);
$(@”view:’UINavigationItemButtonView’ touch”);

[self deleteTestUser];

Over the weekend I did some work on UISpec…mostly getting the “redo” feature working.  Redo let’s you rerun a chained set of queries that will return a result based on what is on the screen right now.  Internally the redo feature is being used for traversals and filtering, and I thought it might come in handy in your test scripts.  Here is a simple example:

//First let's grab the last UILabel under the first
//UITableViewCell on the screen
UIQuery *label = app.tableViewCell.label.last;

//Next we will check that the label's text
//is equal to "Start"
[label.should.have text:"Start"];

Now let’s say we execute some code that reloads the screen or goes to a new one, and afterward we want to check the text of a label in the same place as before but on the new screen.

We could find the label again with label = app.tableViewCell.label.last; OR we can do the following instead:

[label.redo.should.have text:"Finish"];

Calling label.redo will actually rerun the code that created it in the first place: “app.tableViewCell.label.last” except now it’s against the new screen. Acts kinda like a template to find the label. It’s not really a template though since you have to run it at least once before a redo can be called. Now it wouldn’t be too difficult to add finder templates to UISpec. Hmmm…maybe I will work on that next weekend:)

Over the weekend I made some small changes to the way traversals work in UISpec. As a result the following method names have changed:

  • descendants is now descendant
  • parents is now parent
  • children is now child

Calling these methods return exactly what you would except them to. The method parent finds the parent view, child gets you the first child view, and descendant retrieves the first descendant view. Of course each result is wrapped in a UIQuery object. And if you want all the views and not just the first, call the method all afterward. Here are some examples

//Finds the first child of the
//first UITableViewCell

//Finds the all child views under
//the first UITableViewCell

//Touches all the child views under the first UITableView

//Both of these will find the first child UILabel
//under UITableViewCell
[app.tableViewCell.child view:@"UILabel"];

//Both of these will find all the child UILabels
//under UITableViewCell
[app.tableViewCell.child view:@"UILabel"].all;

//And of course the default way still works the same

//Finds the first descendant UILabel 
//under UITableViewCell

//Finds all the descendant UILabels
//under UITableViewCell

Also the documentation has been updated to reflect these changes.

For UISpec I needed a an easy way to create and cache a method call on an object. I know that NSInvocation is the class to go with, but it’s very cumbersome to create and use. Luckily I found the blog post Callable Objects in Cocoa, which pointed me in the right direction, so I took what they suggested one step further and made it dynamic using message forwarding.

Say you have an address book object that models a list of addresses and you want to “record” a call that does a find by first and last name and “play” the call later.

//Create a Recordable instance with the addressBook as its target
Recordable *dvd = [Recordable withTarget:addressBook];

//Record the call you want to make
[dvd findAddressByFirstName:@"Joe" lastName:@"Smith"];

//Play it to get the results of the call
Address *address =;

Pretty nifty huh? Here is the source code. Enjoy!


@interface Recordable : NSObject {
	id target, play;
	NSInvocation *invocation;

@property(nonatomic, retain) id target;
@property(nonatomic, retain) NSInvocation *invocation;
@property(nonatomic, readonly) id play;




  <li>import "Recordable.h"</li>

@implementation Recordable

@synthesize target, invocation;

+(id)withTarget:(id)target {
	return [[[self alloc] initWithTarget:target] autorelease];

-(id)initWithTarget:(id)_target {
	if (self = [super init]) { = _target;
	return self;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
	return [target methodSignatureForSelector:aSelector];

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation setTarget:target];
    [anInvocation retainArguments];
    [anInvocation setReturnValue:&self];
    self.invocation = anInvocation;

-(id)play {
	id value;
	[invocation invoke];
	NSString *returnType = [NSString stringWithFormat:@"%s", [[invocation methodSignature] methodReturnType]];
	if (![returnType isEqualToString:@"v"]) {
		id value;
		[invocation getReturnValue:&value];
		return value;
	} else {
		return nil;

-(void)dealloc { = nil;
	self.invocation = nil;
	[super dealloc];


Tags: ,

We are getting close to releasing our new open source behavior driven development framwork for the iPhone called UISpec. It is modeled after RSpec for Ruby, but is optimized for Objective-C programming with UIKit. We have the new google code site up which you can check out here. More to come soon.

  • Dave: I can tell you're a ruby guy because you forgot the 'return' keyword. Thanks for the tip though!
  • Chandrashekhar H M: Hi, Thanks its working fine in iOS 6 but not in iOS 7.0. Any Suggestion on this.
  • Coeur: To change a rootViewController, without all this TVNavigationController : myNewRoot = [[UIViewController alloc] init]; myNavigationController.view