Code Reading: Shared Examples in Kiwi
Having examined the motivations for using shared examples and how they're implemented in RSpec, we're ready to examine how we can port the same functionality to Kiwi. This post takes approximately 7 minutes to read, and covers:
- How XCTest discovers instances of XCTestCase
- How Kiwi builds a suite of examples by hooking into this discovery
- How Kiwi implements shared examples
Readers familiar with using Kiwi to run Objective-C unit tests will get the most out of this post.
The Gist
Kiwi and RSpec are different beasts. RSpec is a pure testing framework that
defines its own methods for test detection, while Kiwi runs alongside XCTest.
Kiwi hooks into XCTest test detection to build example groups. Shared examples
can be implemented just as in RSpec: by storing context nodes in a global
registry and inserting them where itBehavesLike is used in the tests. A macro
is used to ensure shared examples are registered before test execution.
Note that this post covers shared examples, a feature not yet in the official Kiwi release. See the pull request for information on when it might be available.
How XCTest discovers tests
XCTest is the framework behind the testing tools built into Xcode 5. Two classes relevant to this post are:
XCTestCase: An individual test case.XCTestSuite: A collection of test cases.
If you've used XCTest before, you know that in order to implement test methods,
you need to subclass XCTestCase and write methods that begin with -test. By
default, XCTestSuite collects all subclasses of XCTestCase included in the
test target. For each test method, it creates an instance of the test case and
sends it the -invokeTest message. This executes the test case's -setUp
method, then the test method, and finally the test case's -tearDown method.
XCTest is practically identical to SenTestingKit, the testing framework bundled with versions of Xcode 4.6 and prior–just replace
XCTestCasewithSenTestCase, and so on, in the explanations above. In posts from now on I'll only mention the points at which they differ.
How Kiwi hooks into XCTest test detection
Normally, XCTestSuite executes methods beginning with -test in each
XCTestCase. Alternatively, subclasses of XCTestCase can override the class
method +testInvocations to return a different set of methods to run during
the test.

In fact, this is exactly what KWSpec does. When +testInvocations is
called,
each KWSpec subclass creates an instance of
KWExampleSuite.
In order to do so, KWSpec has the Kiwi DSL written between the SPEC_BEGIN
and
SPEC_END
macros evaluated by a builder
class
called KWExampleSuiteBuilder. Functions like
describe
and
context
are C functions that add
nodes–that
is, blocks of code with a name parameter–to a stack maintained by the suite.
Blocks passed to it functions are also turned into KWItNode objects and
added to the stack.
When the example suite is finished constructing its stack of nodes, it returns
an array of "dummy"
invocations
that match the number of KWItNode objects in the stack. Each of these
invocations also carries a reference to the it
node
it represents, via a KWExample object.
When the -invokeTest method is called on each KWSpec, Kiwi grabs the
KWExample associated with the current
invocation
being called. It then executes the KWItNode
example
by using the -[KWContextNode performExample:withBlock:]
method.
This method uses recursion to move up the context
stack.
The recursion exits when the entire context for the it node has been
reconstructed, and the assertions in the it block are finally
executed.

How shared examples are injected into the context stack
The previous post in this series described how RSpec shared
examples are stored in a global registry. When RSpec encounters an
#it_behaves_like method, it inserts the shared example as a context node in
that spot.
Kiwi shared examples are implemented in the same way. The sharedExamplesFor
method
registers an instance of KWSharedExample in a global registry, an instance of
KWSharedExampleRegistry. The itBehavesLike
method
inserts the shared example as a context block in the current stack.
Differences between RSpec and Kiwi implementations
Unfortunately, because Kiwi must adhere to the test execution flow imposed by
XCTest, the implementation of shared examples isn't as clean as it is in RSpec.
Whereas RSpec can load each spec file and process the shared examples in
advance, Kiwi loads test case files in an order determined by XCTestSuite. As
a result, special precautions must be taken to ensure that shared examples are
registered before any example groups are executed.
The workaround for this limitation is to use the SHARED_EXAMPLES_BEGIN and
SHARED_EXAMPLES_END
macros.
These define an arbitrary category and overload its +load
method.
This method is called when the class or category is added to the Objective-C
runtime. This occurs before any tests are run, so it's an adequate place to
register any shared examples:
SHARED_EXAMPLES_BEGIN(ReusableSpecHelpers)
sharedExamplesFor(@"something reusable", ^(Class describedClass) {
// ...
});
SHARED_EXAMPLES_END
Of course, no one wants more macros in Kiwi. So it's worth considering whether there's a better way of ensuring shared examples are registered before any specs are run.
This concludes our exploration of shared examples in RSpec and Kiwi. Hopefully you got as much out of reading this series as I did writing it! If you have any feedback, please contact me on Twitter.