Clean Kiwi Tests
In the past year, I’ve really grown to appriciate testing an Android application using Robolectric. Looking at iOS, I would be satisfied to use the same technology on that platform. But alas there is not Roblectric for iOS. Enter Kiwi.
Having experimented with Kiwi for the past few weeks, I feel that it’s definitely an adequate unit-testing framework for iOS applications.
A great thing about Robolectic is the freedom that programmers have to make tests readable. That wasn’t the case for those of us that tried writing unit tests using Android’s testing framework. With Robolectic, I can look at a test and know exactly what is going on without the syntax getting in the way. The same can’t be said for Kiwi. The block syntax can be ugly if you’re not used to it. Here’s a Robolectric test.
1: @Test
2: public void itAddsPlayerToDatabaseWhenDoneButtonIsPressed() {
3: startActivity();
4: assertThat(getNumberOfPlayersFrom(database), equalTo(5));
5: enterTextIntoUsernameField(“New User”);
6: doneButton.performClick();
7: assertThat(getNumberOfPlayers(database), equalTo(6));
8: assertTrue(databaseContains("New User"));
9: }
The above test should be pretty easy to figure out. It’s really an integration test, but in this case the backend database is simply an in memory data structure. Typcially I would mock out the calls to the database in order to create a true unit-test, but the point here is to show how easily Robolectric makes it to create readable tests for Android. These tests seem to be endlessly refactorable and common functionality can be pulled out to common classes. Reading a test method can be like reading psedo-code if it was refactored as such.
With Kiwi and iOS its a bit different, mainly because of the Rspec framework on which Kiwi relies. Blocks could be resued to execute routine setup, but the syntax is a big funkier and those blocks are limited to the same spec file.
One thing to do is to define an @interface and @implementation file in the same class as the spec is defined. This allows you to create a wrapper for the ViewController class under test. We would then have the following structure for our .m file.
1: @interface
2: @end
3: SPEC_BEGIN(SpecName)
4: SPEC_END
5: @implementation
6: @end
We use the @implementation file to create the view controller that we are testing. One of the cool things about Robolectric is that it lets you simulate interaction with UI elements. We can do this in iOS, but we must call -loadView. And since a lot of us do our setup in -viewDidLoad, we have to call that too.
In Robolectric we must do something similar, and we can do it by using the create() method of the ShadowActivity class.This method essentially looks like Robolectric.shadowOf(activity).create(). If we aren’t using a version of Robolectric that has the create() shadow class method, we would have to explicitly call lifcycle methods until we call the one that calls setContentView();
In iOS we set the content view by calling -loadView. Setup for Kiwi tests can become quite large quickly if we’re calling lifecycle methods each time we enter a new describe block.
Instead of explicitly calling those lifecycle methods in each beforeEach block in our Kiwi tests, we can simply send the alloc and init message on a class that wraps the view controller under test. We can have this test class create the view controller we’re testing and call those lifecycle methods on it. This should save a few lines of code.
Below is how we would implement the @interface and @implementation files:
1: @interface TestViewControllerTester : NSObject
2: {
3: TestViewController *testController;
4: }
5: @property (strong, nonatomic) TestViewController *controller;
6: @end
7: SPEC_BEGIN(TestSpec)
8: // spec code here
9: SPEC_END
10: @implementation TestViewControllerTester
11: @synthesize controller;
12: -(id)init
13: {
14: self = [super init];
15: if(self)
16: {
17: testController = [[PRViewController alloc] init];
18: [self setController:testController];
19: [[self controller] loadView];
20: [[self controller]viewDidLoad];
21: }
22: return self;
23: }
24: @end
Assuming we did some of our initialization in the viewDidLoad method and we’re not using a wrapper class, our specs could look like the following setup:
1: SPEC_BEGIN(TestSpec)
2: describe(@"When the test view controller is created", ^{
3: __block TestViewController *testViewController;
4: beforeEach(^{
5: testViewController = [TestViewController alloc] init];
6: [testViewController loadView];
7: [testViewController viewDidLoad]
8: });
9: context(@"the ui elements are in the correct state", ^{
10: it(@"the stop button is disabled", ^{
11: [[theValue([testViewController stopButton] isEnabled]) should] equal:theValue(NO)];
12: })
13: });
14: });
15: SPEC_END
By following the pattern described above, the same spec could look like:
1: SPEC_BEGIN(TestSpec)
2: describe(@"When the test view controller is created", ^{
3: __block TestViewControllerTester *testViewControllerTester;
4: beforeEach(^{
5: testViewControllerTester = [TestViewControllerTester alloc] init];
6: });
7: context(@"the ui elements are in the correct state", ^{
8: it(@"the stop button is disabled", ^{
9: [[theValue([testViewControllerTester stopButton] isEnabled]) should] equal:theValue(NO)];
10: });
11: });
12: });
13: SPEC_END
In this example we only save a couple of lines, but in a world where the setup for one view controller is particularly complex, perhaps one involving delegates and several outside dependencies, we can greatly reduce noise in our tests by putting as much of that setup as possible into the Tester class. Imagine if there are several describe blocks in the same test file. That would be a lot of complex and messy setup. Since the tester class is basically a wrapper for the class we want to test, we can reduce a lot of setup to one line in the spec. For instance, we can call methods on the tester class and make those methods look cleaner than calling a method from the actual class we would like to test. More on that, and more, in the future.