Announcing new WatchKit testing tools in PivotalCoreKit

January 14, 2015 Wiley Kestner

We’re happy today to announce an open source tool that makes writing tests for WatchKit apps possible. The WatchKit testing tools we’re releasing today are part of PivotalCoreKit, our helper library for iOS projects.

Suppose you want to write a unit test for the following willActivate method:

@implementation MyInterfaceController
- (void)willActivate
{
    [super willActivate];
    [self.label setText:@"Yay WatchKit!"];
}
@end

Using PivotalCoreKit’s new WatchKit testing tools, your test for this method might look something like this:

#import "Cedar.h"
#import "MyInterfaceController.h"
// #import "PCKInterfaceControllerLoader.h" <-- Only needed when using CocoaPods

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

SPEC_BEGIN(MyInterfaceControllerSpec)

describe(@"MyInterfaceController", ^{
__block PCKInterfaceControllerLoader *loader;
__block MyInterfaceController *subject;

    beforeEach(^{
        NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
        loader = [[PCKInterfaceControllerLoader alloc] init];
        subject = [loader interfaceControllerWithStoryboardName:@"Interface" identifier:@"myId" bundle:testBundle];
    });

    it(@"should show the correct text", ^{
        [subject willActivate];
        subject.label should have_received(@selector(setText:)).with(@"Yay WatchKit!");
    });
});

SPEC_END

This test is written using Cedar BDD framework, a free, open-source testing framework for Objective-C code developed at Pivotal Labs. We prefer writing tests with Cedar, but it is also perfectly possible to write a similar test for your WatchKit app using the XCTest tools that ship with Xcode.

Setting up the testing environment

First, configure your production app with a Watch App and Watch Extension target
by following Apple’s instructions for setting up a WatchKit app.

Next, if your project doesn’t already use Cedar, follow the Cedar installation instructions to install Cedar and add a Cedar Spec Suite target to your host project. (Note: It’s possible to use a Cedar Spec Bundle target instead, but more difficult to configure initially.)

If you’re using CocoaPods, you can install the WatchKit tools by adding the following lines to your Podfile.

target 'MySpecs' do
  pod 'PivotalCoreKit/WatchKit/WatchKit'
end

If you’re not using CocoaPods, then you should link the PCK WatchKit static framework to your test target:

  1. Add PivotalCoreKit’s WatchKit.xcodeproj to your host project (so that it appears within your “Project navigator”)
  2. Add PivotalCoreKit’s WatchKit framework build target to your test target’s “Build Phases > Target Dependencies”
  3. Link PivotalCoreKit’s WatchKit.framework (important: do NOT link Apple’s real WatchKit!) in your test target’s “Build Phases > Link Binary With Libraries”
  4. Add the classes you wish to test to your test target (Don’t forget to add the Interface.storyboard from your `Watch App` target to your test target as well)

After completing these steps, you can write the first test for your WatchKit App!

How do the testing tools work?

There are two core components of the WatchKit testing library.

The first core component is the helper class PCKInterfaceControllerLoader. This is the class that helps you instantiate your app’s WKInterfaceController subclasses in a testing environment. Normally
we would simply call [[MyInterfaceController alloc] init], but unfortunately in our test environment, directly instantiating production WatchKit classes caused bad access exceptions. In production, all WKInterfaceController subclass instances are created behind the scenes and configured using properties set in the Watch App’s main storyboard file.

Calling interfaceControllerWithStoryboardName:identifier:bundle: on the PCKInterfaceControllerLoader instance in your test will return an instance of your WKInterfaceController subclass. Under the hood, the loader is using your storyboard as a blueprint to construct the controller instance. The controller instance it provides you with will have all the properties and outlets that you’ve hooked up in your storyboard, and configure them exactly as your storyboard specifies. The only difference between the instance that the loader provides you with and a real production instance of your controller is that the loader’s instance is actually a test double. In fact, all of the instances of labels, buttons, etc. on your controller instance are also test doubles.

These test doubles are the second core component of Pivotal Core Kit’s WatchKit testing library. We’ve made an interface-identical copy of every class in Apple’s WatchKit. The test doubles are a specific kind of test double called “spies”. All messages sent to the spy are stored as NSInvocation instances in a sent_messages array on the spy instance itself. Your tests will assert how your production code changes your controller and its properties (for instance, changing the text on a label by asserting that a label instance received the setText: message).

Using the Cedar have_received matcher is a good way to find out what messages have been sent to test doubles. The have_received matcher can take any of PCK’s fake WatchKit objects as its first argument, so you can use Cedar’s have_received matcher assert what messages they have (or have not)
received. In the example above, we use the have_received matcher to test whether a label has some expected text set on it:

subject.label should have_received(@selector(setText:)).with(@"Yay WatchKit!");

For numerous examples of this technique and for examples of how to test all of your WatchKit interface objects, feel free to refer to the specs we used to test-drive the testing toolkit itself.

Pivotal Core Kit’s WatchKit testing tools are still under heavy development so questions, suggestions, and pull requests are welcome. We hope that these tools will help you develop the next generation of Apple Watch apps and we welcome your feedback to help make our tools even more helpful. Happy testing!

About the Author

Biography

Previous
MADlib 1.7 Release—Adding Generalized Linear Models, Decision Trees, and Random Forest
MADlib 1.7 Release—Adding Generalized Linear Models, Decision Trees, and Random Forest

The new release of MADlib 1.7 includes several new features, namely generalized linear models, decision tre...

Next
All Things Pivotal Podcast Episode #11: So What Happens When I Push An App Anyway?
All Things Pivotal Podcast Episode #11: So What Happens When I Push An App Anyway?

One of the benefits of PaaS is that a whole swathe of complex detail is taken care of by the platform itsel...