Mocking singleton / sharedInstance in Swift

I have a class that I want to test with XCTest, and this class looks something like this:

public class MyClass: NSObject { func method() { // Do something... // ... SingletonClass.sharedInstance.callMethod() } } 

The class uses a singleton, which is implemented as follows:

 public class SingletonClass: NSObject { // Only accessible using singleton static let sharedInstance = SingletonClass() private override init() { super.init() } func callMethod() { // Do something crazy that shouldn't run under tests } } 

Now for the test. I want to check that method() really does what it should do, but I do not want to call the code in callMethod() (because it does some awful async / network / thread data that should not run under MyClass tests, and this will cause the test to fail).

So what I would like to do basically:

 SingletonClass = MockSingletonClass: SingletonClass { override func callMethod() { // Do nothing } let myObject = MyClass() myObject.method() // Check if tests passed 

This is obviously not valid Swift, but you get the idea. How can I override callMethod() just for this particular test to make it harmless?

EDIT: I tried to solve this using the dependency injection form, but ran into big problems. I created my own init method, which will be used for tests, so that I can create my objects as follows:

 let myObject = MyClass(singleton: MockSingletonClass) 

and let MyClass look like this

 public class MyClass: NSObject { let singleton: SingletonClass init(mockSingleton: SingletonClass){ self.singleton = mockSingleton } init() { singleton = SingletonClass.sharedInstance } func method() { // Do something... // ... singleton.callMethod() } } 

Mixing in the test code with the rest of the code is what I find a bit uncomfortable, but everything is fine. The BIG problem was that I had two singleton built this way in my project, both referencing each other:

 public class FirstSingletonClass: NSObject { // Only accessible using singleton static let sharedInstance = FirstSingletonClass() let secondSingleton: SecondSingletonClass init(mockSingleton: SecondSingletonClass){ self.secondSingleton = mockSingleton } private override init() { secondSingleton = SecondSingletonClass.sharedInstance super.init() } func someMethod(){ // Use secondSingleton } } public class SecondSingletonClass: NSObject { // Only accessible using singleton static let sharedInstance = SecondSingletonClass() let firstSingleton: FirstSingletonClass init(mockSingleton: FirstSingletonClass){ self.firstSingleton = mockSingleton } private override init() { firstSingleton = FirstSingletonClass.sharedInstance super.init() } func someOtherMethod(){ // Use firstSingleton } } 

This created a dead end when one of the singletones where it was first used is because the init method will wait for the init method of the other, etc ...

+5
source share
3 answers

I ended up solving this with code

 class SingletonClass: NSObject { #if TEST // Only used for tests static var sharedInstance: SingletonClass! // Public init override init() { super.init() } #else // Only accessible using singleton static let sharedInstance = SingletonClass() private override init() { super.init() } #endif func callMethod() { // Do something crazy that shouldn't run under tests } } 

This way, I can easily make fun of my class during tests:

 private class MockSingleton : SingletonClass { override callMethod() {} } 

In tests:

 SingletonClass.sharedInstance = MockSingleton() 

Only test code is activated by adding -D TEST to the "Other quick flags" in the build settings for the target application test program.

+4
source

Your singletons follow a very common pattern in Swift / Objective-C lines of code. Also, as you saw, it is very difficult to test an easy way to write unchecked code. There are times when a singleton is a useful example, but my experience is that most uses of the template are actually poorly suited to the needs of the application.

The singlet +shared_ style of Objective-C and the constant Swift class singlets usually provides two behaviors:

  • This can lead to the fact that you can create an instance of only one instance of the class. (In practice, this often does not apply, and you can continue to use additional instances of alloc / init , and the application depends on the developers, following the agreement on exclusive access to the shared instance using the class method.)
  • It acts like a global one, allowing access to a shared instance of the class.

Behavior # 1 is sometimes useful, while Behavior # 2 is simply global with a diploma in template design.

I will resolve your conflict by completely removing global variables. Add your dependencies all the time, not just for testing, and think about the responsibility you give in your application when you need something to coordinate any set of shared resources that you enter.

The first pass with injection addiction throughout the application is often painful; "but I need this instance everywhere!". Use it as a hint for design revisions, why do so many components access the same global state, and how can it be modeled instead of providing better isolation?


There are times when one copy of some altered general condition and one instance of one instance is perhaps the best implementation. However, I find that in most examples this still fails. Developers usually look for a joint state, but with some conditions: only one screen, until an external display is connected, there is only one user, until they log out and go into the second account, there will be only one queue of network requests, until you find the need for authentication against anonymous requests. Similarly, you often need a shared instance before executing the following test case.

Given that a small number of singleton appears to be using fault tolerant initializers (or obj-c init methods that return an existing shared instance), it seems that developers are happy to share this state by convention, so I see no reason not to enter a shared object and write easily verifiable classes instead of using global variables.

+4
source

I had a similar problem in my application, and in my case it was wise to use Singletons for these specific services, as they were proxies for external services, which were also single point.

I ended up implementing the Injection Dependency model using https://github.com/Swinject/Swinject . It took about a day to implement about 6 classes, which seems to take a long time to include this level of modularity. This made me think about the dependencies between my service classes, and it made me specify them more clearly in the class definitions. I used ObjectScope in Swinject to tell DI frameworks that they are single: https://github.com/Swinject/Swinject/blob/master/Documentation/ObjectScopes.md

I was able to get my singleton and pass them to the mock versions of my unit tests.

Thoughts on this approach: it seems to be more error prone since I could forget to properly initialize my dependencies (and would never get a runtime error). Finally, this does not prevent someone from simply creating an instance of my service class directly (which was like the whole singleton point), since my init methods had to be published to the DI Framework to create objects in the registry.

0
source

Source: https://habr.com/ru/post/1241854/


All Articles