What’s the difference between a mock and a stub, and how do you use them in Swift?

Northern Mockingbird: Public Domain

(Northern Mockingbird. Public domain.)

Lots of people use the terms “mock” and “stub” interchangeably, and there’s a good deal of confusion around them. But mocks aren’t stubs, as Martin Fowler says — they behave a bit differently.

So what’s the difference between a mock and a stub, and how do you use them to test your code?

Stubbing in Swift

A stub just returns canned data. That’s all. Assuming you have a BlogClient that fetches a Blog (synchronously, for simplicity), its stub might look like this:

class BlogClientStub: BlogClient {
    override func fetch() -> Blog {
        return Blog(title: "Roadfire Software")
    }
}

As you can see, the fetch method in our stub just returns a canned blog — the same one every time it’s called.

Now, let’s say we have a BlogViewModel that uses a BlogClient to fetch a Blog and we want to test it. If the fetch is asynchronous and slow, you’d want to avoid calling the actual implementation in your unit tests. So you might test it with a stub like this:

func test_title_with_stub() {
    let stub = BlogClientStub()
    let viewModel = BlogViewModel(client: stub)
    let title = viewModel.title()
    XCTAssertEqual("Roadfire Software", title)
}

Here we’re just creating an instance of our BlogClientStub and passing it to our BlogClientViewModel. When we ask our view model for the title, we expect that we’ll get the title of the Blog in the stub — in this case, “Roadfire Software”.

Stubs are simple and straightforward — they’re essentially the simplest possible implementation of a method and return the same canned data every time.

Mocking in Swift

Mocks, on the other hand, are a bit more complex. They verify that a particular method was called. We can create our own mock in Swift like this:

class BlogClientMock: BlogClient {
    var fetchWasCalled = false
    override func fetch() -> Blog {
        fetchWasCalled = true
        return Blog(title: "Another iOS Blog")
    }
}

This looks a lot like our stub, but it has a little extra functionality. It tracks whether the fetch method was called with the fetchWasCalled property. It’s initialized to false, indicating that fetch hasn’t been called yet, and then flipped to true as soon as the fetch method is called.

How would we use this in a test? Like this:

func test_title_with_mock() {
    let mock = BlogClientMock()
    let viewModel = BlogViewModel(client: mock)
    let _ = viewModel.title()
    XCTAssertTrue(mock.fetchWasCalled)
}

In this test, we’re creating an instance of our mock, then passing it to the BlogViewModel. When we call the title method on our view model, we expect the view model to call fetch on our BlogClientMock. This gives us a little more confidence in our view model — we know that the method was called as expected. If we wanted to be even more sure that things are working correctly, we could also make an assertion about the title like we did in our stub test.

You can take mocks even further — if you have a method with parameters, for example, your mock can track the values you passed as parameters to the method. This allows you to verify not only that the method was called, but that it was called with the correct parameter values. And furthermore, you can track the number of times the method was called with a property (like fetchCallCount, for example) and verify that fetch was called the correct number of times.

Should you stub or mock?

This brings us to the question: should you stub or mock when you’re dealing with asynchronous code in Swift?

In general, I prefer to stub. To me, mocks feel a bit brittle — they know a lot of detail about the implementation of the class I’m testing. I’d rather just verify that the output of my public methods are correct, and with stubs I can do that fairly simply.

There are plenty of other options if you don’t want to mock or stub — sometimes I find fakes to be useful. A fake is a working implementation of a class that you couldn’t use in production, like an in-memory database. I’ve used a fake for testing UserDefaults before — it’s fairly easy to set up and can sometimes be nicer than mocks or stubs.