Singletons are the scourge of good software design. But everyone uses them, especially in iOS development. This post will show one way to make singletons testable in Swift applications.

Let’s be real, we’re not building web apps. Long running objects are part of the game for client development. State happens. Singletons are a simple way to share an object throughout your app without putting the effort into a cleaner way of managing dependencies. They are ubiquitous in 3rd party components. Even if your own code is pristine, you still have to manage singletons.

How can we singletons better?

We can’t make singletons better. But we can make the code using them testable. This will at least make things less painful.

Testable singletons in Swift

Let’s take a singleton class


public class ScarySingleton {
    
    class var sharedInstance: ScarySingleton {
        
        struct Static {
            static let instance: ScarySingleton = ScarySingleton()
        }
        return Static.instance
    }
    
    public var someimportantValue: String? = nil
    
    public func doSomethingImportant() {
        NSLog("So important")
    }
}

Many devs would use this class like this

public class ViewController: UIViewController {

    public override func viewDidLoad() {
        super.viewDidLoad()
        
        // I wish I knew how to test you!
        ScarySingleton.sharedInstance.someImportantValue = "Yes!!!"
        ScarySingleton.sharedInstance.doSomethingImportant()
    }
}

This is bad. This singleton just made this class hard to test. For two reasons:

  1. We can’t access the sharedInstance to know if doSomethingImportant has been called. The class is a black box.
  2. We have no way to isolate our test cases. One test isn’t too bad. For a single test you could verify the someImportantValue property was set as expected. But this breaks down when you add more tests. The state from the previous test is preserved through the next test runs. This means your previous test can easily mess up your current tests. That’s bad.

In Objective-C we could use runtime-fu and stub out the sharedInstance for our tests. That’s not available to us in Swift. Even in Objective-C it’s a painful way to write tests.

So how can we test this class?

  1. Create a property that holds a reference to the singleton
  2. Create a stub class derived from the singleton
  3. Create a separate init function for test code
  4. Use the test initializer to inject our stub object instead of the singleton

Wait. What?


public class ViewController: UIViewController {
    
    // We set the scarySingleton here for most use cases
    private let scarySingleton = ScarySingleton.sharedInstance
    
    public init(scarySingleton: ScarySingleton) {
        // test code injects it's dependencies
        self.scarySingleton = scarySingleton
        super.init(nibName: nil, bundle: nil)
    }

    public required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        
        // So testable!
        scarySingleton.doSomethingImportant()
        scarySingleton.someImportantValue = "So important!"
    }
}

We now have a property that holds the value of the ScarySingleton. We access the singleton through the property, not its staticInstance.

During normal object creation the staticInstance is used. When testing the class, we use the other initializer.

Our tests would look like this


class ViewControllerTests: XCTestCase {
    
    var sut: ViewController!
    var scaryStub: ScaryStub!
    
    class ScaryStub: ScarySingleton {
        var doSomethingImportantCalled: Bool = false
        
        override func doSomethingImportant() {
            doSomethingImportantCalled = true
        }
    }

    override func setUp() {
        super.setUp()
        scaryStub = ScaryStub()
        sut = ViewController(scarySingleton: scaryStub)
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }

    func testDoSomethingImportantIsCalled() {
        // This is an example of a functional test case.
        let view = sut.view
        
        XCTAssertTrue(scaryStub.doSomethingImportantCalled)
    }

    func testImportantValueIsSet() {
        let view = sut.view
        XCTAssertEqual(scaryStub.someimportantValue!, "So important!")
    }
}

What is this wizardry?

This is a common pattern in .NET and Java projects that don’t use Dependency Injection (DI) containers. People who love DI containers think it’s the worst pattern ever. But almost no one uses DI containers in iOS so adjust your outrage accordingly. This technique is commonly called Poor Man’s Dependency Injection or Bastard Injection. These are unfortunate terms but provided for the readers ability to learn more about the topic.

For iOS specific reading, Jon Reid did an excellent write up of Dependency Injection in the August edition of objc.io here.

Without the default implementation, the above pattern would just be Constructor Injection.

Purist Gripes:
Proper DI wouldn’t create a default implementation. It should always be injected.
Multiple initializers make things more confusing

Pragmatic Pluses:
We get the ease of singletons without sacrificing testability

Sample code for this project here.