With Swift, a singleton initializer is called twice when running XCTest unit tests.
No problems with Objective-C, though, the init() method is only called once, as expected.
Here's how to build the two test projects:
Create an empty Objective-C Project with tests. Add following bare-bones singleton:
#import "Singleton.h"
@implementation Singleton
+ (Singleton *)sharedInstance
{
static Singleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Singleton alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"%@", self);
}
return self;
}
@end
In the application delegate add a call to the singleton like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[Singleton sharedInstance];
return YES;
}
Also add a call to the singleton to the generated test class:
- (void)testExample {
[Singleton sharedInstance];
// This is an example of a functional test case.
XCTAssert(YES, @"Pass");
}
If you add a breakpoint to the singleton's init
method and run the tests, the breakpoint will only be hit once, as expected.
Now do the create a new Swift project and do the same thing.
Create a singleton, add the test target to its Target Memberships
class Singleton {
class var sharedInstance : Singleton {
struct Static {
static var onceToken : dispatch_once_t = 0
static var instance : Singleton? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = Singleton()
}
return Static.instance!
}
init() {
NSLog("\(self)")
}
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
Singleton.sharedInstance
return true
}
func testExample() {
// This is an example of a functional test case.
Singleton.sharedInstance
XCTAssert(true, "Pass")
}
This time, if you add a breakpoint to the singleton's init
method and run the tests, the breakpoint will be hit twice, first from the app delegate, then from the test case, i.e. you'll have two instances of the singleton.
Am I missing anything?
Since application module and tests module are separated modules, when you add the Singleton.swift
file to test target member, YourApp.Singleton
and YourAppTest.Singleton
are not same class. That's why init
called twice.
Instead of that, you should import
your main module in your test file:
import YourAppName
func testExample() {
// This is an example of a functional test case.
Singleton.sharedInstance
XCTAssert(true, "Pass")
}
and your Singleton
class must be declared as public
. see Swift, access modifiers and unit testing
public class Singleton {
public class var sharedInstance : Singleton {
struct Static {
static var onceToken : dispatch_once_t = 0
static var instance : Singleton? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = Singleton()
}
return Static.instance!
}
init() {
NSLog("\(self)")
}
}
Don't forget to remove Singleton.swift
from test target membership.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With