Unit Testing in Swift

Swift, being all the rage these last four days, has definitely livened up our programming chat room quite a bit. With cautious optimism, we (Betsy and Brad) delved into the Xcode beta, curious about the state of testing with Swift. For the purposes of this article, we’ll only cover the basics of unit testing with XCTest.framework.

A Simple Test

Just to get things rolling, we wrote a Plain Old Swift Object to represent firewood.

As you can see, we have a simple initializer and one function to burn the firewood. Now, let’s write a simple test to assert that burning the firewood chars it.

importXCTestclassSimpleFirewoodTests:XCTestCase{functestBurningActuallyChars(){letfirewood=Firewood()firewood.burn()assert(firewood.charred,"should be charred after burning")}}

Using a Common Subject

If we have multiple tests using the same setup for an object under test (or “subject” if we use Ruby’s RSpec parlance), we don’t want to have to repeat the initialization inside each test. That’s just not DRY. So, how do we do this?

After some trial and error involving opaque runtime errors, we learned the answer definitely does not involve overriding the init method for XCTestCase subclasses. Do not go there. Hic sunt dracones.

Instead, you’ll want to declare and initialize an instance variable with let (or var) as shown:

classSimpleDRYFirewoodTests:XCTestCase{letfirewood=Firewood()functestBurningActuallyChars(){firewood.burn()assert(firewood.charred,"should be charred after burning")}}

This may seem a little counterintuitive, because you might ask “What happens to the firewood object in subsequent test blocks if I mutate it in one of them?” That takes us to the next section.

How XCTest Handles Multiple Tests in One TestCase

XCTest avoids using dirty objects from previous tests by instantiating a brand-new XCTestCase object for each test function. That is, if you have three tests in your XCTestCase subclass, the framework will instantiate three objects of the whole XCTestCase subclass. Not only that, but it does all of this work before the Test Suite even starts. Observe:

classFirewoodTests:XCTestCase{letfirewood=Firewood()functestNewObjectUnderTestForEachTest1(){println("testing that we have a new object in 1st test")assert(!firewood.charred,"shouldn’t be charred before burning")firewood.burn()}functestNewObjectUnderTestForEachTest2(){println("testing that we have a new object in 2nd test")assert(!firewood.charred,"shouldn’t be charred before burning")firewood.burn()}functestBurningActuallyChars(){firewood.burn()assert(firewood.charred,"should be charred after burning")}}

Since our Subject prints out “initializing our firewood” each time init is called, we can see where that happens in relation to everything else in a test run. Here’s the beginning of the log output:

Note how three Firewood objects are created, presumably by three different FirewoodTests objects before the Test Suite even begins its output.

Conclusion

We hope this article will help you avoid some of the missteps that we took. It seems unit testing in Swift is implemented well despite the dearth of documentation. Does anyone else have adventures to share about testing with Swift?