How do you mock a singleton class

This is a question that came up in my mentorship program Slack team. I think it’s worth a blog post.

I’m trying to set up some tests according to the templates and the new book. I’m struggling with one small detail. In the book, you set up an ordersWorkerSpy, which is derived from a normal class. My question is: What to do if that class is a singleton, which is accessed through something like ordersWorkerSpy.shared?

Let’s work through a code example.

The test subject

Let’s define a view controller that depends on a worker. You’ve seen plenty of examples where the interactors hand off the work to one or more workers. But the worker concept applies equally to presenters and view controllers too. Even a worker can have workers. It’s just a way of delegating complex work to other, smaller components. Breaking up a large class into multiple, smaller classes.

The ViewController class has an instance variable worker that is initialized to the Worker singleton. The viewDidLoad() method calls doSomeWork() which, in turn, calls doSomething() on the worker object. Pretty typical.

The dependency

In the Worker class, the singleton mechanism is done by defining a static constant sharedWorker, and making the default init() private. This has become the simplest, Swiftest way of creating a singleton class.

This works perfectly in the app code. However, in the test target, when we define the WorkerSpy to inherit from Worker, we have to remove the private keyword so that we can override it. If not, the compiler will complain. This is why that line is commented out above. Yes, it’s a tradeoff that we make between enforceability and simplicity. But it’s a small sacrifice in order to keep our test code straightforward to write, as you’ll see next.

The test double

Now we can create the test double in the usual, familiar way.

We make the WorkerSpy inherit from Worker. And then define the sharedWorkerSpy static constant in the same way we did for the test subject. Now, we can override init() to do nothing. The rest is the usual stuff. Override the doSomething() method and record the method invocation.

The test method

Finally, here is the code for the complete test class. But I’m just going to focus on talking about the test method. You can read about how this view controller test is structured and set up in this post.

In the testDoSomeWork() test method, we use the WorkerSpy singleton instead of the Worker singleton – sut.worker = workerSpy. When the test executes, the doSomething() method in the spy will be invoked instead of the real one.

As you just saw, singletons are nothing special in terms of unit testing.

Want to learn more about writing unit tests for your app? Check out my new book – Effective Unit Testing.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

3 Comments

  1. By setting init() as non-private class becomes non-singleton,
    nothing stops developer to use it (init) directly in the code later on.

  2. This is fine but what happens if your Worker is used by multiple classes and you want to design a test abstracted around a general use case ? Most often the shared instance of the singleton is being used globally as opposed to being declared as a type variable.

    Also why would you write the { on a new line ? 😀

  3. Sorry but this approach has a little to do with singleton. It’s typical case of dependency injection, which is not commonly used on iOS platform. The question about mocking a global singleton still remains not answered.

Leave a Comment

Your email address will not be published. Required fields are marked *