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:
- We can’t access the sharedInstance to know if doSomethingImportant has been called. The class is a black box.
- 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?
- Create a property that holds a reference to the singleton
- Create a stub class derived from the singleton
- Create a separate init function for test code
- 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.
I believe this approach could be made a bit easier to test through using protocols. Extract the necessary functionality out to a protocol and depend only on that protocol. Retroactive modeling with extensions can be used to make this possible for third party libraries as well. One advantage is being able to create lightweight test mocks that only include stub the needed functionality and don’t incur the potentially heavier setup and state of doing a subclass.