(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.