State of Mocking in Swift Tests
Mocking and stubbing in tests is useful to verify behavior and replace collaborating objects with simpler alternatives. Since Swift is statically typed, you can’t use a mocking library anymore. But it’s trivially simple to write simple mock objects as subclasses in place:
class Bar { func bar() {} }
func foo(bar: Bar) {
bar.bar()
}
class FooTest: XCTestCase {
func testFoo() {
class MockBar: Bar { /* ... */ }
let barDouble = MockBar()
let baz = foo(barDouble)
XCTAssertNotNil(baz)
}
}
You can declare subclasses in the scope of the test method and override only the minimum necessary parts.
I like to take the additional safety measure to exclusively descend from Null-Objects in such cases. If foo()
called another method on its Bar
parameter than expected, we end up with the real behavior and real side-effects. The Null-Object pattern ensures the parent class does nothing for every method, not just the one we override in a specific test.
Using subclasses private to a method is clever, but it doesn’t cut it. You can write stubs easily, but you cannot write mock objects which record incoming calls for verification without a lot of hassle.
XCTestExpectation
would work to verify calls – but it’s meant for asynchronous tests. Calling waitForExpectationsWithTimeout(0)
just to verify if a synchronous expectation is fulfilled is overkill. (Using larger timeouts is a bad idea as it slows down your test suite without any benefit.)
Also, I get segmentation faults all the time:
func testFoo() {
let expectation = expectationWithDescription("called")
class MockBar: Bar {
private override func bar() {
expectation.fulfill() // comment-out this line to not raise segfaults
}
}
let barDouble = MockBar()
foo(barDouble)
waitForExpectationsWithTimeout(0) { error in }
}
Same for booleans, which otherwise sound like a reasonable alternative:
func testFoo() {
var expectation = false
class MockBar: Bar {
private override func bar() {
expectation = true
}
}
let barDouble = MockBar()
foo(barDouble)
XCTAssertTrue(expectation)
}
So we end up with the more clumsy variation of boolean properties:
func testFoo() {
class MockBar: Bar {
var didCallBar = false
private override func bar() {
didCallBar = true
}
}
let barDouble = MockBar()
foo(barDouble)
XCTAssertTrue(barDouble.didCallBar)
}
This sucks, but it works.
Meanwhile, I filed a Radar and will now figure out if this is worth changing struct
s to class
es in production code. Because struct
s cannot be sub_classed_.