Stub vs Spy vs Mock: Aren't they all just the same thing?

Stub vs Spy vs Mock: Aren't they all just the same thing?

When implementing tests you may have needed to use some sort of test doubling.

In this article, I want to briefly talk about the differences between the concepts of mock, spy and stub, and how and when to use them.

Let's start with the most commonly heard concept: the mock.


Mock

As a software developer is most likely that you had contact with a somewhat complex app that relies on an expensive or complicated resource.

In iOS development it's common to have some form of a Database using Core Data to store some form of data and manipulate it either to the backend or present it to the user.

If you need this database for a test should you use the real database? It depends, if you're implementing integration tests you might need to use the real database but, with unit tests, you might lose a lot of time just to set it up.

So in this case you might need a substitution that acts like a database.

From the author, Kent Beck on Test-Driven Development by Example (p.146):

The solution is to not use a real database most of the time. Most tests are written in terms of an object that acts like a database, but is really just sitting in a memory.

We can even say that mocks are like stubs since we can configure a mock for a specific reply that you want. For example, we can set up a database to return a successful response without going through the process of calling the real database. However, a mock goes a little bit further than that since they should save all interactions and do analysis afterwards (from Effective Software Testing a developer's guide by Maurício Aniche, Chapter 6 page 144).

Essentially, a mock is a test double that you set your expectations upfront and as you can see from other resources it might be more complex than the other concepts that we will briefly talk about in this post.

When talking about mocking it's most likely you will use a Mock framework made by you or a 3rd party that will do most of the stubbing and mocking to improve your development testing.


Stub

As iOS Developers it's very common to implement features that request an API to receive and handle data. When you're implementing unit tests for the class that is responsible for handling this data it's most likely that you won't do real requests to the API. This would increase the runtime of the test and decrease the performance. The following scenario would be applied:

We can stub or give a hard-coded answer to the call you would be performing to the API. This is a simplified version and usually doesn't have a working implementation.

From from Effective Software Testing a developer's guide:

Stubs are the most popular type of simulation. In most cases, all you need from a dependency is for it to return a value so the method under test can continue its execution.

For example, if you were testing if you are correctly mapping the list of objects from an API request you can stub the response of the API request to be successful and return a list of objects. By creating a stub you would always return a deterministic response making your tests more reliable.


Spy

Spies are very powerful when you need to spy on a dependency when implementing tests. These classes usually wrap themselves around an object to spy or observe their behaviour. Contrary to the stub, it will rather record the interactions.

One possible use for a spy is to know how many times was the request to a backend called on a given class. One possible implementation of the Spy could be as follows:

protocol HTTPClient {
    func get(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
}

class HTTPClientSpy: HTTPClient {
    var messages = [Messages]()

    enum Messages: Equatable {
        case get(with: URL)
    }
    
    func get(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
        messages.append(.get(with: url))
    }
}

Mocks vs Stubs

It's very common to mix everything up into the same thing, I've seen these 3 concepts being used interchangeably but one thing that is very important to state is that all of these concepts are test doubles which is a generic term for a pretend object in place of a real object to test something.

According to Martin Fowler, and Gerard Meszaro:

Of these kinds of doubles, only mocks insist upon behavior verification. The other doubles can, and usually do, use state verification.
Mocks actually do behave like other doubles during the exercise phase, as they need to make the SUT believe it's talking with its real collaborators - but mocks differ in the setup and the verification phases.

So mocks, like other test doubles make the SUT believe it's talking with real collaborators, they differ on the setup and verification.

Conclusion

To implement tests in any software, it's very common to need some sort of simulated response from an external factor to the element we're testing. We would heavily decrease the quality of the automated tests if we kept using the real responses. Mocks, Stubs, Spies and other test doubles that weren't mentioned in this article are useful to simulate different test cases to make any implementation cleaner.


References

Test-Driven Development by Example by Kent Beck

Effective Software Testing a developer's guide by Maurício Aniche

Essential Developer Course by Caio Zullo and Mike Apostolakis

Mocks Aren’t Stubs
Explaining the difference between Mock Objects and Stubs (together with other forms of Test Double). Also the difference between classical and mockist styles of unit testing.

Thank you for taking the time to read this article.

If you enjoyed my work and want to stay updated on future projects, don't hesitate to connect with me on GitHub or LinkedIn. Thank you 🙏🏻