facing to test problems

mocking-singletons-with-ocmock

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:

1
2
3
4
5
6
7
8
9
10
11
static NSNotificationCenter mockNotificationCenter = nil;

@implementation NSNotificationCenter (UnitTests)

+(id)defaultCenter {

return mockNotificationCenter;

}

@end

With this category, any time the code under test calls [NSNotificationCenterdefaultCenter], 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
-(void)testShouldPostNotification {

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 [NSNotificationCenterdefaultCenter] 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+(id)createMockNotificationCenter {

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:

-(void)testShouldPostNotification {

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@implementation NSNotificationCenter (UnitTests)

+(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:

+(id)mockNotificationCenter {

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.

transfer from here

Private method

1
[_mokedetective performSelector:@selector(handleGps:) withObject:info];

OCMock AFNetworking

1
2
3
4
5
6
7
8
9
_mokeSession = OCMPartialMock([XYZSessionManager sharedSessionManager]);

[XYZSessionManager setSharedInstance:_mokeSession];

OCMStub([_mokeSession simplePost:XYZHttpPostBeacons withParam:[OCMArg any] success:[OCMArg any] failure:[OCMArg any]]).andDo(^(NSInvocation *invocation){
[_mokeDetective setValue:@"123" forKey:@"floorId"];
[_mokeDetective setValue:@"456" forKey:@"siteId"];
[_mokeDetective setValue:[NSNumber numberWithBool:isGetBeacons] forKey:@"isGetBeaconsHttps"];
});

Test method

1
2
3
4
//ON
OCMVerify([_mokedetective performSelector:@selector(sendBeaconsHttps:)]);
//OFF
OCMReject([_mokedetective performSelector:@selector(sendBeaconsHttps:)]);