Unit Testing - Mocking In Swift

Mocking is a useful tool when writing unit tests. Due to limitations in the current swift version, there aren’t any real mocking frameworks like the ones you see for Java and Obj-C. That said, there are work arounds. Here’s a quick one when you need a one-off:

Method to test:

func crossDissolve(toIdentifier identifier: StoryboardIdentifier) {
    let nextViewController = viewController(forIdentifier: identifier)
    nextViewController.modalPresentationStyle = .fullScreen
    nextViewController.modalTransitionStyle = .crossDissolve
    show(nextViewController, sender: self)
}

This just performs a simple cross-dissolve between two view controllers (the one it’s on to the new one).There are four things to validate:

  1. The UIViewController passed to show(_:sender:) is the one expect.
  2. The sender is correct
  3. That the presentation style is fullScreen
  4. The transition style is crossDissolve

Since it doesn’t return any values I’m going to have to capture them instead. The method under test is crossDissolve(…) so I don’t want to change that behaviour. Everything else is fair game though. In this case, if I intercept the call to show(…) I can capture the parameters passed and validate them.

Since this is a one-off I can nest a class inside my test and capture the values appropriately. Then I can fill in the test.

func testCrossDissolve() {
    class MockSut: UIViewController {
        var showViewController: UIViewController?
        var showSender: Any?
        override func show(_ vc: UIViewController, sender: Any?) {
            showViewController = vc
            showSender = sender
        }
    }
    let mockSut = MockSut()
    mockSut.crossDissolve(toIdentifier: .gameViewController)
    XCTAssertNotNil(mockSut.showViewController as? GameViewController)
    XCTAssertEqual(mockSut.showSender as? UIViewController, mockSut)
    XCTAssertEqual(mockSut.showViewController?.modalPresentationStyle, .fullScreen)
    XCTAssertEqual(mockSut.showViewController?.modalTransitionStyle, .crossDissolve)
}

So, we’re creating a subclass of UIViewController and overriding a method that is called by the method we are interested in testing. Then we can use assertions to complete our test.

Of course, this could get messy if we had a bunch of test cases which needed to handle overrides. In that case I’d move the MockSut class out of the function and into the parent class. If I needed it outside of this specific set of tests, I’d move it into its own class so it could be used in multiple places.