Mocking singletons with OCMock
Although Graham Lee might not approve, I occasionally use the singleton pattern, particularly for managing access to my data models.
Singletons in Cocoa are accessed using the following convention:
+(UserModelManager *)sharedManager {
if (sharedManager == nil) {
sharedManager = [[super allocWithZone:NULL] init];
}
return sharedManager;
}
This makes it challenging to unit test classes that depend on the singleton. Normally when you test class A which depends on class B, you can pass A a mocked instance of B, configured appropriately for the test. But if B is a singleton, A’s only way to instantiate it is through that sharedB instantiator, so your test gets the one and only real instance of the singleton.
There are certainly singleton-ish ways to design around this constraint, but even if your classes don’t use the singleton pattern, you almost certainly depend on one of the singleton classes in the iOS API, like UIApplication or NSNotificationCenter.
Enter categories
The simplest way around this problem is to create a category on the singleton in your test code that overrides the singleton instantiator. In Objective-C, a category is mechanism for adding or overriding methods in existing classes. Using a category, we can modify the behavior of the defaultCenter method just within the scope of the unit tests to return a mock version:
@implementation NSNotificationCenter (UnitTests)
+(id)defaultCenter {
return mockNotificationCenter;
}
@end
With this category, any time the code under test calls [NSNotificationCenter defaultCenter]
, it will get value of mockMotificationCenter instead. If you do nothing, it’ll be nil, so it effectively just becomes a message sink. But your test can create a mock using OCMock that expects the notification and verifies that it is issued:
mockNotificationCenter = [OCMockObject mockForClass:[NSNotificationCenter class]];
[[mockNotificationCenter expect] postNotificationName:@"some notification"];
[myObject doSomethingThatPostsNotification];
[mockNotificationCenter verify];
mockNotificationCenter = nil;
}
Note that with this approach, you need to be sure to set the mock instance to nil at the end of your test. OCMocks are autoreleased, so if the test case still has a reference to the released version, you’ll get an EXC_BAD_ACCESS error the next time [NSNotificationCenter defaultCenter]
is called. A good place to do that is in your tearDown method, so it’s set back to nil after each test.
Making the mock available to other tests
Now that we can mock the singleton in a single test case, we should make it available to all of our unit tests. I have a base test case class that all of my test classes inherit from, which is where I import OCMock and OCHamcrest and do other global testing setup. We can move the static variable and category up to that class, and create static methods for setting the variable:
mockNotificationCenter = [OCMockObject mockForClass:[NSNotificationCenter class]];
return mockNotificationCenter;
}
+(id)createNiceMockNotificationCenter {
mockNotificationCenter = [OCMockObject niceMockForClass:[NSNotificationCenter class]];
return mockNotificationCenter;
}
These methods both set the static variable and return the mock to the caller, for setting expectations and verifying. The “nice” version will silently swallow unexpected messages, where the regular version will throw an exception if it receives an unexpected message. Don’t forget to set it back to nil in tearDown, and call [super tearDown]
in the test class’s tearDown method. Now we can update the test case above to:
mockNotificationCenter = [BaseTestCase createMockNotificationCenter];
[[mockNotificationCenter expect] postNotificationName:@"some notification"];
[myObject doSomethingThatPostsNotification];
[mockNotificationCenter verify];
}
But sometimes I want the real NSNotificationCenter
The main drawback to this category is that it will always override the original. That may not be an issue for notifications, but when testing your own singletons, you’ll need to be able to test the actual class. But now that you’ve overridden the instantiator in a category, you can’t just call the original instantiator if the static variable is nil. Or can you? Fortunately, Matt Gallagher created some nice macros that you can use to conditionally invoke the original.
Using Matt’s invokeSupersequentNoArgs
macro, we can change the category method to the following:
+(id)defaultCenter {
if ([BaseTestCase mockNotificationCenter] != nil) {
return [BaseTestCase mockNotificationCenter];
}
return invokeSupersequentNoArgs();
}
@end
Note we also have to add a class method to retrieve the mock:
return mockNotificationCenter;
}
Now when [NSNotificationCenter defaultCenter]
is invoked, the test case first checks to see if a mock has been set. If it has, the mock is returned. Otherwise, the original, overridden method is invoked.
Conclusion
The singleton is a design pattern you should use with care, as it certainly makes you jump through a few more hurdles when testing. But with a bit of careful setup, your singleton dependencies can (and should) be tested and verified.
20 Comments
jianhua
December 2, 2011Two questions;
1. Mock for mockNotificationCenter, why not use method observerMock available in OCMock? It seems more easy.
id aMock = [OCMockObject observerMock]
2. mockNotificationCenter = nil; doesn’t work, dues to mockNotificationCenter is a static variable, need to use static method to set it.
My implementation:
@interface NSNotificationCenter (UnitTests)
+(id)createMockNotificationCenter;
+(id)createNiceMockNotificationCenter
+(void)releaseInstance;
@end
//———————————
static NSNotificationCenter * mockNotificationCenter = nil;
@implementation NSNotificationCenter (UnitTests)
+ (id)defaultCenter {
if(mockNotificationCenter != nil){
return mockNotificationCenter;
}
return invokeSupersequentNoArgs();
}
+(id)createMockNotificationCenter {
mockNotificationCenter = [OCMockObject mockForClass:[NSNotificationCenter class]];
return mockNotificationCenter;
}
+(id)createNiceMockNotificationCenter {
mockNotificationCenter = [OCMockObject niceMockForClass:[NSNotificationCenter class]];
return mockNotificationCenter;
}
+(void)releaseInstance {
mockNotificationCenter = nil;
}
@end
Christopher Pickslay
December 2, 2011Thanks for the feedback!
1) I do sometimes use observerMock, but generally more in functional tests. In a unit test I generally want to avoid side effects, like other observers’ methods being called, so I feel like it’s cleaner to just mock the NSNotificationCenter. This way you can also verify your addObserver: and removeObserver: calls.
2) It depends how you set up your test classes. In my example, the static variable is defined inside my test class, and I can assign to it directly both from the NSNotificationCenter implementation and my tests. If it were defined in a separate scope, then you’re correct that you’d need a method on that class to set it from your test.
What I actually do is define all my static singletons in a base test class, which all of my tests extend. Then the tearDown method in the base test class looks like this, so I know the state is clean at the end of each test:
-(void)tearDown {
mockApplication = nil;
mockNotificationCenter = nil;
mockPaymentQueue = nil;
mockGanTracker = nil;
[super tearDown];
}
jianhua
December 14, 2011Thanks you kindly response.
For #1, agree with you, id aMock = [OCMockObject observerMock] can only mock a small part of actions.
For #2, now I see your design, it is cool, clean. I like it.
Plus with another question.
For singleton mock, how do you think of partial mocks?
id aMock = [OCMockObject partialMockForObject:anObject]. It is particularly usefully if you don’t want to mock your instance methods completely.
Do experiments and find that partialMockForObject also has its own defect, when used for static variable, it will remember original stub or expect.
Really tough, no better solution found so far.
Christopher Pickslay
December 14, 2011Interesting. I hadn’t thought about using partial mocks for singletons, though I see how it could be a problem since the singleton instance is generally going to be instantiated once. I suppose you could fork OCMock and add a “removeAllExpectations” method or something.
jianhua
December 15, 2011Thanks Pickslay’s kindly response.
I have fired a question to OCMock dev group, try to discuss this issue with them.
jianhua
December 18, 2011No response from OCMock development side, fire a question on stackoverflow. http://stackoverflow.com/questions/8556527/using-partialmockforobject-to-do-singleton-class-mock-how-to-create-a-method-li
Jon
July 10, 2013It’s nasty but you can just call init on the partialMock. init has the effect of resetting all saved recorders.
Nick Black
March 25, 2013Hi- I know this is an old post- but I’m trying to get this pattern set up- where you declare the static singletons in a base class. Could you post an example code of how this works?
Thanks
Christopher Pickslay
March 28, 2013Nick, take a look at https://www.dropbox.com/s/eifcrt34es11drz/BaseTestCase.m
jianhua
December 15, 2011Hey Pickslay;
Sorry to trouble you again.
I still don’t understand how the static variable is defined inside your test class, what’s up in BaseTestCase? How do they communicate with category ones? Could you give me more tips?
Thanks very much.
Christopher Pickslay
December 16, 2011Sure, take a look at https://www.dropbox.com/s/eifcrt34es11drz/BaseTestCase.m. I don’t actually have statics in my test classes–I wrote it that way in the post to make it more clear. All the statics are in my base test case, which all my tests extend.
jianhua
December 18, 2011Hey Pickslay;
Got it by another PC. One question, why do you declare it as a static method? I think (-) interface method will be easier to use, such as [self create….], how do you think of it?
+(id)createMockNotificationCenter;
+(id)createNiceMockNotificationCenter;
Finally appreciate your help very much.
jianhua
December 18, 2011Hey Pickslay;
Plus another question. Why not do mock verify on dealloc?
-(void)tearDown {
[mockApplication verify];
mockApplication = nil;
…
}
Christopher Pickslay
December 19, 2011I think tearDown should be reserved for cleanup. If you want to verify a mock, you should do it explicitly in your test. The test is documenting what you think the code should do, so the verify is an explicit statement of what you expect.
mimc
August 13, 2012How does the complier know which implementation to use since category methods are added to the class? Defining a method in both a class and a category of that class just causes a duplicate implementation warning.
Christopher Pickslay
August 13, 2012Yeah, that warning is new in Xcode 4.4. I believe that when a category implementation is compiled, it has the same effect as calling
class_replaceMethod
at runtime–the original implementation is just replaced. The compiler isn’t compiling the implementation into the calling classes. It’s adding selectors, and the messages to these overridden methods are sent to whatever implementation exists at runtime.My understanding is that the reason overriding a method defined in the same class is potentially dangerous is that the original method might also be defined in a category, and it’s undefined which of multiple category implementations will end up in the compiled output.
All that said, we’ve moved away from this approach. If you set up your unit tests correctly, it’s now not too hard to write your tests to use the SDK implementations of singleton classes like NSNotificationCenter.
If you cant, or don’t want to use the SDK implementation, one approach is to wrap the class method in a convenience method in your class, and use partial mocks to mock that convenience method.
As to mocking my own singleton classes, I’ve moved from the approach in this post to making the singleton instance settable.
Nick Black
April 2, 2013Thanks for this article. I had a general newbie question about categories- if I introduce a category for use in unit tests, and #include the category in my test class, how does that category get picked up by the the class I am testing without me also including it in that classes header file?
e.g. In your example- [myObject doSomethingThatPostsNotification], how does the system know to invoke the category code- as opposed to the default notification center?
Thanks in advance,
Nick
Christopher Pickslay
April 4, 2013It would be worthwhile to read Matt Gallagher’s explanation of how invokeSupersequent() works. When you add a method in a category, it gets compiled in and added to a list of the class’s methods, which you can inspect using class_copyMethodList. As I understand it, a method added in a category is added in such a way that it will appear earlier in that list, and the runtime will find it rather than the original method when looking up an appropriate selector.
You shouldn’t normally try to override a method in a category, as if two different categories try to override the same method, it’s undefined which implementation will be invoked at runtime. But it works just fine for mocking singleton initializers, and I’ve yet to come across a situation where some other category was trying to override the singleton initializer, because, why would they?
Andy O
July 11, 2014Great article, thanks.
What do you mean by “you need to be sure to set the mock instance to nil at the end of your test. OCMocks are autoreleased, so if the test case still has a reference to the released version, you’ll get an EXC_BAD_ACCESS error…”
I’m fighting a nasty EXC_BAD_ACCESS crash in my large unit test base now. I use similar pattern to what you describe here, and for usage of that, or any other OCMock I create (nice mocks, partial mocks, and reg mocks) – I never set any of them to nil at the end of the test. Should I be doing that even in the days of ARC?
Christopher Pickslay
July 17, 2014Andy, I don’t really use that approach, but what’s going on in the sample code is it overrides `[NSNotificationCenter defaultCenter]` to return the mock. If you don’t un-set the variable holding the mock, the mock will be autoreleased (even under ARC), and defaultCenter will return a pointer to junk.
But I don’t as a general practice nil out mocks at the end of a test. For normal mock usage, that shouldn’t be necessary.