views:

143

answers:

3

I have a singleton class, and can't unit test their code.

I have test like:

    Db *db = [[Db alloc] initWithName:@"sample.db"];

[db createDb];

STAssertEquals([db existDb],YES,@"The db is not created!");

But only work the first. When the second is executed, I always get "null" from the initWithName method. When I remove the singleton support code, all work as expected.

I could hack the testing (but I don't know how right now) but wonder if exist a "poper" way to deal with this.

The singleton is located here: http://code.google.com/p/chibiorm/source/browse/trunk/src/Db.m

+2  A: 

Singletons are hard to unit test and are sometimes the result of poor design.

My recommendation would be to think hard about whether you really need a singleton in the first place.

Kyle W. Cartmell
A: 

Maybe you could use the Factory pattern and create a factory that hands out only one instance (effectively your singleton). Then the implementation is not a singleton and you can unit test it to your hearts content.

The only drawback is that you are not protected by the language to create your own instance if you don't retrieve it from the factory. In C++ you may overcome this by making the constructor private and the factory and the unit test friends. I am not sure if Objective-C has a similar feature.

lothar
You know a sample implementation? The factory code I know is used for other things...
mamcx
A: 

I think you shouldn't return nil on the second alloc but raise an exception. If you want to use a singleton you should not try to create two :).

However, if I decide to create a singleton my class looks like:

@implementation MySingleton

static id _instance = nil;

+ instance
{
    if (_instance == nil) {
        // alloc/init
        _instance = [[self alloc] init];
        …
    }
    return _instance;
}
…
@end

As you can see I am not enforcing that there may never be more than one instance. Instead I am using the convention to get the instance only with the instance method.

Tilo Prütz
Surely that should be [[self alloc] init], right?
Chuck
It could. As the ellipsis shows there is something missing. I omitted it intentionally since I often use init functions with parameters like `initWithName:`.Now - when thinking over it - it seems to me, that you are right and it should stand there to show that the instance is initialized correctly.
Tilo Prütz