Mocking in Swift

Mohammed Abdullatif
The Startup
Published in
9 min readMay 1, 2020
Photo by Keila Hötzel on Unsplash

When I started learning about what defines a good unit test, an important concept to remember was that good unit tests should be isolated and that the goal of a single unit test is to verify that a specific unit of our code works properly while being isolated from the rest of the system. This means that if for example, we have a Shop class that contains a method sellItem(:item) and if we want to write a unit test that will test the behavior of sellItem(:item), our unit test should not care at this point about the implementation details of the item argument that sellItem(:item) accepts. If our unit test cares about the implementation details of the item argument, this means that our unit test is not isolated and actually at this point, we might not be able to call it a unit test anymore. It’s more of an integration test now.

To make our unit test isolated and not forced to work with other parts of our code that might introduce other problems, we have to fake those other parts that we don’t want to depend on or care about. During my development years, there has been lots of confusion about what does faking means here. After reading a few books and articles on the subject, I found that there are many ways to fake the behavior of the objects that we don’t care about. Those objects, in this case, can get many different names: “mocks, stubs, dummies, fakes, spies,..”. I used to get confused all the time when I heard those words. At some point, they all seemed very similar to me. Many times I used to refer to stubs as mocks and vice versa, but thankfully I learned more about the differences as I progressed in my career. I hope that I have a better understanding of those concepts now and that I can help you understand them too.

Test Doubles

Gerard Meszaros’s has a nice definition for what does faking an object mean in his book “xUnit Test Patterns”. He uses the term “Test Double” to refer to any kind of fake or pretend object that is used in place of a real object for testing purposes.

There are different types of “Test Doubles” that we might need for writing our tests. While the definitions can be slightly different among developers, I would love to share my understanding in this article and hear any other definitions or thoughts about them. This is a list of the most common “Test Doubles” used in the testing world:

  • Dummy Object: An object that does nothing. The main importance of creating a dummy object is for our code to compile. We can have some parameters to fill and in this case, we need to create some dummy objects so that our code can successfully compile. Those dummy objects can be of the type class or struct and they might conform to any number of protocols but they don’t have any functionality.
  • Fake Object: An object that is an actual working implementation of a class for example, but the implementation takes some sort of shortcut that makes testing easier. The most common example is an in-memory database fake object. Fake objects can make our tests run faster. An in-memory database runs faster than a real one.
  • Stub Object: An object that always returns fixed data. For example, if you want to write a test that verifies the correct creation of a model object after decoding some JSON data, it’s better in this case to create an object that will always return a fixed JSON response. This helps to make your tests faster and always reliable.
  • Mock Object: An object that uses behavior verification. Mock objects are widely used to verify that an object was initialized successfully, that specific methods were called inside the object or that some methods were called a certain number of times. Mock objects track all the data so that you can verify it later but they don’t do any meaningful action. Mocks are very powerful because we set them up with a series of expectations, trigger some action, and then we will be able to inspect all the work that happened in a sealed black box with no actual work happening. They give us just enough feedback to build real objects.
  • Spy Object: A spy object is similar to a mock object except that it can make actual work happen. A spy object tracks the behavior of an object (to check if specific methods were called successfully or called a certain number of times) and also does an actual work (like sending a real network request).

Regardless of which implementation you choose to create your test double, all test doubles need to conform to the correct protocols required for them to stand in place for the real type. All types of test doubles are important, but “mocks” and “stubs” are maybe the two most common types that are being used in the world of testing.

In this article, we will talk more about mocking in Swift and see some examples.

Mocking

You probably have heard the term “mocking” so many times until now. It is so popular today and is widely used that many developers use it to refer to the act of creating a test double of some sort.

If we recall, the main job of a mock object is to verify that some sort of behavior happened on this mock object. Mocks simulate the behavior of a real object but they don’t cause any actions to take place.

Remember: An important characteristic of a good unit test is that it is isolated and that it tests one single unit of code at a time. Things outside the tested code are faked or simulated because unit tests shouldn’t have dependencies on outside systems. If we have one object that we want to test and this object depends on some other objects, to make sure that a unit test is focusing only on verifying the behavior of our object and doesn’t get complicated by using instances of the dependencies, we tend to simulate the behavior of those dependencies, i.e. mocking them.

Let’s start with an example: people buying books from a BookStore. Our BookStore has an inventory of 1000 books and every time a customer buys a book the inventory’s count gets decreased by one. This is how our Buyer class may look like:

class Buyer {    func buy(bookStore: BookStore) {        bookStore.executePurchase()
}
}

And this is how our BookStore class may look like:

class BookStore {    var numberOfBooks = 1000    func executePurchase() {        numberOfBooks -= 1    }
}

Testing BookStore by itself is straightforward. We can do the following:

func testExecutePurchaseDecreasesInventoryByOne() {    // given    let bookStore = BookStore()    // when    bookStore.executePurchase()    // then    XCTAssertEqual(bookStore.numberOfBooks, 999)}

What about testing the Buyer class? If we look at buy(bookStore:) method we will realize that our Buyer class has a dependency on the BookStore class. Remember that we want our test to be focused on the buyer and be less distracted by the implementation details of the book store. We want our test to verify that when buy(bookStore:) is called on any Buyer object, then this causes the inventory of the book store to get decreased by one, but we don’t want to use a real object of the book store in this case. To achieve that, we will create a mock class forBookStore, a class that simulates what happens inside the BookStore but it’s smaller, faster, and easier to change for any future testing needs.

Let’s see how can we write a test for Buyer using a mocked BookStore instead of using a real instance of BookStore:

func testBuyerBuyingBookDecreasesBooksInventoryByOne() {    // given    let buyer = Buyer()    let bookStoreMock = BookStoreMock()    // when    buyer.buy(bookStore: bookStoreMock)    // then    XCTAssertEqual(bookStoreMock.numberOfBooks, 999)}

Okay, now we need to create BookStoreMock, a class that will act as a test double for BookStore, meaning that it will be a class that simulates the functionalities that BookStore does but will focus on one aspect. In our case, it will be a class that simulates the book store’s behavior in executing a purchase.

The classic computer science book Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (The Gang of Four Book) mentions that if we want to define one or more behavior for a class, we should then design to interfaces, not implementations. In Swift, this means using protocols rather than concrete types. Creating a protocol gives us the power to create different classes that conform or agree to the same behaviors but implement them differently. For example, we will create a BookStoreProtocol that declares the method executePurchase, and we will create two classes BookStore and BookStoreMock. Both of these classes will conform to BookStoreProtocol, but inBookStore’s implementation of executePurchase, it will choose to write the numberOfBooks when a purchase happens to a local database, while BookStoreMock's implementation of executePurchase will just return a number and writes something to our logs.

Using protocols gives classes the freedom to have different implements for the same behaviors defined in one protocol. This makes creating tests doubles that are needed for writing unit tests easier.

Let’s write a protocol that will define the expected behaviors from the BookStore class:

protocol BookStoreProtocol {    var numberOfBooks: Int { get set }    func executePurchase()}

Now, let’s make our original BookStore class conform to it:

class BookStore: BookStoreProtocol {    var numberOfBooks = 1000    func executePurchase() {        numberOfBooks -= 1    }}

and let’s create our mock class for BookStore :

class BookStoreMock: BookStoreProtocol {    var numberOfBooks = 1000    func executePurchase() {        numberOfBooks -= 1
print("BookStoreMock: The current number of books in the inventory is \(numberOfBooks)")
}}

In our simple example, the implementation of executePurchase is not that different if we compare BookStore and BookStoreMock, but in larger projects, mocked methods tend to have a simpler implementation compared to the concrete implementation.

There’s one more change that we need to do, we need to change the buy method inside our Buyer class to accept an argument of our protocol type BookStoreProtocol instead of the concrete type BookStore. This allows us to call buy(bookStore:) in our production code by passing an instance of BookStore and allows us to call buy(bookStore:) in our unit tests by passing an instance of BookStoreMock. The key player here is our protocol BookStoreProtocol that allowed us to do that. This is how the Buyer class will look like after this change:

class Buyer {    func buy(bookStore: BookStoreProtocol) {        bookStore.executePurchase()
}
}

If you run the unit tests now, they should be green.

Partial Mocks vs. Full Mocks

The approach that we followed in the previous example with our BookStore and BookStoreMock classes is called full or complete mocking because we create completely separate types for our testing purposes. There’s another approach called partial mocking, it involves subclassing a specific type and overriding the parts that we want to simulate in our tests doubles only.

Let’s see how we can implement that. In the scenario of partial mocking, we want our partially mocked class to implement a different behavior for executePurchase method. Instead of decreasing the number of books by 1, we will decrease the number of books by 10 when executePurchase is called. Our unit test should verify that deduction behavior is happening successfully.

This is our partially mocked class:

class BookStorePartialMock: BookStore {    override func executePurchase() {        numberOfBooks -= 10    }}

In this case, our buy(bookStore:) method inside our Buyer class can accept an argument of the type BookStore with no problems:

class Buyer {    func buy(bookStore: BookStore) {        bookStore.executePurchase()    }}

and our unit test now can look like the following:

func testBuyerBuyingBookDecreasesBooksInventoryByOne() {    // given    let buyer = Buyer()    let bookStoreMock = BookStorePartialMock()
// when buyer.buy(bookStore: bookStoreMock) // then XCTAssertEqual(bookStoreMock.numberOfBooks, 990)}

At this point, I hope that I managed to make the mocking concept in Swift a little bit approachable for you. In this article, I shared the basic definition of what a “test double” means and what are the different types of test doubles that we can create. I also talked a little bit about the definition of mocking and why mocking can be useful when we write tests. If you have any questions, suggestions, or improvements I’m always happy to learn more and exchange knowledge with all of the awesome developers everywhere. Please feel free to write a comment, tweet me, or drop me a line!

Resources:

--

--