Test Doubles by example in Swift

Peña Fernando
5 min readFeb 24, 2022

--

While writing your unit tests you need to create the dependencies of the system under test (SUT). Most of the times you choose (or are forced) to create your own version of the depended-on component (DOC), that’s what we call a test double. Think of it as a Stunt Double in movies.

Test doubles are really helpful because they help us simulate the functionality of the DOC that suits our test needs and/or inspect its state. They share the same interface of the real component so our SUT won’t be affected by using a double instead of the real one.

The concept of test doubles has been around for a while, there are a couple of variations that have become pretty standard and Martin Fowler enumerates in one of his articles

Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.

Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example).

Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.

Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don’t expect and are checked during verification to ensure they got all the calls they were expecting.

Source: https://martinfowler.com/bliki/TestDouble.html

Concepts and definitions are really useful but watching them in action can make things clearer. Let’s create an example of each of the different test doubles in a simple real world project.

Stocks Watchlist

I will be working on a project of an editable watchlist of stocks. What’s that?… think about the iOS Stocks App where we have a list of stocks that you can edit (add, remove and reorder stocks).

Here is our code for the watchlist

Entities

Our model entities are a Stock, which consists in a symbol (Ex: AAPL, GOOG…) and the related Quote that contains the market data at a given time.

Interfaces

We know that the watchlist is going to be persisted somewhere and that we will need to consume the latest market data from a server but we don’t want to couple the system with the concrete implementations.

These protocols create the abstraction needed to don’t couple our system with the real components giving us more flexibility.

  • We can replace the implementations without affecting the rest of the system.
  • While testing we can use test doubles instead of the real ones.

This way our WatchlistViewModel doesn’t need to know how the store is implemented. It could use CoreData, Realm, UserDefaults or any other persistence infrastructure but it doesn’t care, it only knows that the store updates and retrieves the watchlist.

The same thing happens with the quotes service, internally it can use URLSession, Alamofire or any other networking framework but from the viewmodel perspective it’s just a service that retrieves the latest quotes for a given list of stocks symbols.

ViewModel

Our WatchlistViewModel has the 2 dependencies that we previously mentioned. We are using dependency injection here so instead of the WatchlistViewModel creating its dependencies they are being provided, this technique is very useful for unit testing because we can easily replace the dependencies with test doubles.

We also have the properties required by the View to render the Watchlist (title, isLoading, errorMessage and stockViewModels) and 2 methods to update and get the watchlist with the latest quotes.

The updateWatchlist method just forward the message to the store. The getWatchlist instead has a little more logic, it gets the list of symbols from the store and call the service to get the market data. It then maps the results to an array of StockViewModels using the help of the StockViewModelMapper.

That was a long introduction, let’s dive into unit testing and explain the different variations of test doubles in this real world example.

Test Doubles

Dummy

Dummy objects are passed around but never actually used. They are used to fill a required dependency of the SUT that we don’t care for our test purposes.

The first thing we would like to test about the WatchlistViewModel are the initial conditions. For this test we would need to create it’s dependencies because they are required, but we actually don’t care about them.

Stub

Stubs provide canned answers to calls made during the test.

Our WatchlistStoreStub let us create a WatchlistStore with any prefilled response that we want, we can emulate an update/retrieve error or return any list of stockSymbols we want.

In our first test we are testing the simplest scenario where we want the store to return an empty list of stock symbols. On the second one we are emulating a generic retrieve error and making sure the viewmodel generates an error message.

Spy

Spies are stubs that also record some information based on how they were called.

Our QuotesServiceStub is recording the calls executed in the quoteCalls array, we are storing the symbols array for every call. This way we can assert in our test that when we call the getWatchlist method the getQuotes method of the service is called too and with the right symbols.

Fake

Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production.

We are creating a fake version of the WhatchlistStore, in our project the Whatchlist needs to be persisted between sessions, but for our testing purposes we can create an in-memory store that’s totally functional but won’t persist the data.

Mock

Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive.

// Given
let mock = WatchlistStoreMock()
mock.expect(...expectations...)
mock.expect(...expectations...)
mock.expect(...expectations...)
let sut = WatchlistViewModel(store: mock, service: DummyQuotesService())
// When
sut.doSomething()
// Then
mock.verify() // ensure expectations were met at the end

We are not providing a real example of a mock because they’re complex to create from scratch, usually you’d use a Mock framework. You can do everything they can with simpler test doubles such as Stubs and Spies making the tests more readable.

Summary

We’ve seen how to create and use Test Doubles in a simplified real world project, not just the definitions but also examples of each one of them, except for the mock one that I don’t recommend to use.

That’s all for now, any feedback is highly appreciated. You can download the code here: https://github.com/fernandopena/stockswatchlist

Links

I would like to thank Caio & Mark and the entire EssentialDeveloper community.

--

--

Peña Fernando

iOS Developer with 10 years of experience that never stops training. I’m passionate for well structured and reusable code.