State of Mocking in Swift Tests

null

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.

strawman
Photo Credit: 稻田 ricefilm Strawman by earl258. License: CC-BY-NC 2.0

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 structs to classes in production code. Because structs cannot be sub_classed_.