Jacob’s Tech Tavern

Jacob’s Tech Tavern

Unit Testing Masterclass: Mocking like a Pro

Write robust and maintainable software using modern language features

Jacob Bartlett's avatar
Jacob Bartlett
Jun 20, 2023

Welcome to Part 2 of my blog series about how to write tests when utilising modern language features like async/await or frameworks like Combine.


  1. Part I Dependency Injection Demystified

  2. Part II Mocking like a Pro

  3. Part III Unit Testing with async/await

  4. Part IV Advanced async testing: Unstructured concurrency

  5. Part V (interlude) — Implement your Data Access layer with Combine

  6. Part VI Combine, async/await, and Unit Testing


Part II - Mocking like a Pro

I appreciate the fact that I’m writing a 6-part blog series about how to write tests, and with 2 posts down I still haven’t shown you a single test - but trust me that I’m giving you the tools to make the actual testing portion an absolute breeze.

What are mocks?

Mocks are versions of your classes and services which conform to the same protocol (interface) as the ‘real’ versions. These are dependency-injected into the classes which you’re testing and allow you fine-tune behaviour. You can do things like:

  • Count the number of times a method is called

  • Pass it some sample data to return for a success-case test

  • Tell it which error to throw to test your exception handling

We use mocks extensively when testing, to ensure that we can test the behaviour of the thing that we’re testing - in unit tests, we don’t care about the behaviour of the thing that we’re injecting.

Quick note on the Test Pyramid

The ‘Test Pyramid’ is an approach to testing:

  • Unit tests at the bottom of the pyramid - these usually only test one class, have lots of mocks, and we write a lot of them

  • Integration tests in the middle of the pyramid - these test the interaction between classes, and we should write a decent number for really critical components like our authentication or core business logic.

  • End-to-end testing at the top of the pyramid - these are a lot slower, test whole features at once, and we write a smaller number of these.

Quick diagram of the Test Pyramid in all its glory

The mock-related thing to note here is that unit tests pretty much exclusively use mocks, whereas in integration tests you might use the ‘real’ version of a service that you inject, to test the real behaviour and interaction between the components.

How do we create mocks?

Creating mocks is pretty straightforward once you have an interface built.

Let’s return to our sample code for Bev. We’re trying to mock out the API, which in Part I we dependency-injected into the Repository class.

Remember, by mocking out the API we should be able to replicate the behaviour of the real API, but control all the results. Here’s the protocol (interface if you’re a fan of Kotlin) for the API:

BeerAPI.swift protocol interface

To make a mock, let’s just define a class, MockBeerAPI, and you’ll see an error in Xcode:

Simply allow Xcode to auto-fill the method required to conform to the protocol, and hey-presto! You’ve got yourself a mock.

In true Art Attack style, here’s one I made earlier:

MockBeerAPI.swift

There’s quite a bit of extra power that I added to the mock - let’s go through it sequentially:

  • I added a public init so that any test class can easily create their own version of this mock.

  • There’s a public stubBeersResponse that returns a Result type - this means I can tell my mock to return either an error or some test data when I set this property.

  • The didGetBeers closure is deferred, which means it gets called after I return from my getBeers() method. This is extremely helpful when dealing with async tests, but fairly advanced stuff which I'll cover in future posts.

  • getBeersCallCount is a good old-fashioned integer property, that simply gets incremented every time the getBeers method is called. This is great for writing the common or garden “did this method call this API” test.

  • Finally, we modify getBeers() to ensure we call the deferred closure, we increment the call count, and by using get() we either return the value set in the stubBeersResponse Result, throws an error, or crashes if this isn’t set. In the sample code, I’ve actually written helper method called evaluate() which causes an XCTest failure instead of a crash if you forget to set this, but I’m leaving it out of this example for simplicity’s sake.

Subscribe for free to continue reading

Enter your email and get every post from Jacob’s Tech Tavern.
Already have an account? Sign in
© 2026 Jacob Bartlett · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture