overwriting a method via stubbing with OCMock - objective-c

I am trying to test that a timer object is stopped after a level is completed.. I have the following code:
-(void)advanceLevel {
int nextLevelId = self.currentLevel.id + 1;
self.currentLevel = [[Level alloc] initWithIdentifier:nextLevelId];
[self.timer stop];
[self prepareLevel];
}
...
The prepareLevel method resets the timer value and calls "start" on it--- so in order to test that advanceLevel actually stops the timer, I need to overwrite the prepareLevel method.
So in my unit test, I did the following:
-(void)testItStopsTheTimer {
[timer start];
id mockGame = [OCMockObject partialMockForObject:game];
[[[mockGame stub] andReturn:nil] prepareLevel];
[game advanceLevel];
STAssertFalse(timer.active, nil);
}
Which results in XCode saying "testItStopsTheTimer (Gametests) failed. Ended up in subclass forwarder for Game-0x12383060......."
So, is it not possible to stub out an existing method and replace it with nothingness?

What you're trying to do is definitely possible with OCMock.
What is the method signature for prepareLevel? If it returns void, your mock setup should be:
[[mockGame stub] prepareLevel];
not:
[[[mockGame stub] andReturn:nil] prepareLevel];

What you are trying to do is possible with OCMock. In your test code one lines stands out:
id mockGame = [OCMockObject partialMockForObject:game];
The question is, where does "game" come from? Is the same instance used in multiple tests? The error you are seeing can be caused by the following sequence: you are using expect on a partial mock, the expected method is called, then you are called the method again, but now there's no expectation left and the partial mock doesn't know what to do.
UPDATE: I have just changed OCMock so that in such cases the mock simply forwards the method to the real object. See: https://github.com/erikdoe/ocmock/commit/e03d4fe74465b4fe3fa33552e036de8986f8dec2

Related

How to use OCMock to verify static methods

I am trying to use OCMock library. I am trying to create mock of class object, but it is failing to verify the method. I am unable to understand why the tests are failing.
#interface MyClass:NSObject
+(void) someMethod;
#end
#implementation MyClass
+(void) someMethod
{
NSError* error = nil;
if (![Utility isValidPropWithError:&error])
{
[Logger log:LoggerLevelWarning message:[error localizedDescription] className:className];
}
}
#end
Test :
-(void)testIfLoggerIsset{
id partialMockLogger = OCMClassMock([Logger class]);
id partialMockUtility = OCMClassMock([Utility class]);
id partialMockClass = OCMClassMock([MyClass class]);
NSError *error = nil;
OCMExpect([partialMockUtility isValidPropWithError:&error]);
[MyClass someMethod];
//This works fine.
OCMVerifyAll(partialMockClass);
NSString *className = #"classname";
//This is failing...
OCMVerify([partialMockUtility isValidPropWithError:&error]);
OCMVerifyAll(partialMockUtility);
//This is failing...
OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]);
[partialMockUtility stopMocking];
[partialMockLogger stopMocking];
}
In the above code, although [Utility isValidPropWithError:&error]; is called OCMVerify([partialMockUtility isValidPropWithError:&error]);is failing.
Several things here:
First, OCMVerify([partialMockUtility isValidPropWithError:&error] is failing because you are expecting the address of the NSError object you created in the test to be passed to isValidPropWithError:, but in MyClass +someMethod you are creating a different NSError object. The addresses of two different objects will not be the same.
To fix this, change your expectation and verification to:
OCMExpect([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg anyPointer]]);
OCMVerify([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg
and just ignore the actual value of the parameter and expect that it's going to be an NSError pointer (since you're creating it inside of someMethod, there's no way to know what it's going to be before you call the method).
Second, since you are already explicitly verifying +isValidPropWithError, OCMVerifyAll(partialMockUtility) isn't going to verify anything. You should either explicitly verify all of your expectations, or simply use OCMVerifyAll(partialMockUtility) and let it verify all your expectations and don't bother with expecting the specific call. OCMVerifyAll will verify everything you expect on the mock object you give it. This isn't going to cause a test failure - both calls will pass, since you've already verified the call the first time, the call to OCMVerifyAll() isn't going to have anything to verify, so it will pass.
Last, OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]); is failing because you didn't set an expectation for it.

iOS - Unit Testing Asynchoronous code

The part of a method that I am trying to test is as follows:
- (void)configureTableFooterView {
dispatch_async(dispatch_get_main_queue(), ^{
self.tableView.tableFooterView = nil;
if ([self.parser.resultSet isLastPage]) {
return;
}
});
}
I have written the unit test as follows:
- (void)testTableFooterViewConfigurationAfterLastPageLoaded {
id mockTableView = OCMClassMock([GMGFlatTableView class]);
OCMExpect([mockTableView setTableFooterView:[OCMArg isNil]]);
id resultSet = OCMClassMock([GMGResultSetInfo class]);
OCMStub([resultSet isLastPage]).andReturn(YES);
OCMStub([self.mockParser resultSet]).andReturn(resultSet);
id partialMockSUT = OCMPartialMock(self.sut);
OCMStub([partialMockSUT tableView]).andReturn(mockTableView);
[self.sut configureTableFooterView];
OCMVerifyAllWithDelay(mockTableView, 2.0);
//OCMVerifyAllWithDelay(partialMockSUT, 2.0);
}
I have another test in the same class which is testing the same things from with in the dispatch_async call on the main thread. The test expectations and verification setup in that test match this one. While that test passes, this one gets stuck in an infinite loop at the delayed verification step.
Interestingly, if I only run this 1 test, it passes with out any problems. Its only when this test is run with other tests that I see the problem.
UPDATE:
In unit test, execute the block passed in queue with dispatch_asyc
This is a much more relevant post. However, this fails almost in the exact same way as the original test method:
- (void)testTableFooterViewConfigurationAfterLastPageLoaded {
id mockTableView = OCMClassMock([GMGFlatTableView class]);
OCMExpect([mockTableView setTableFooterView:[OCMArg isNil]]);
id resultSet = OCMClassMock([GMGResultSetInfo class]);
OCMStub([resultSet isLastPage]).andReturn(YES);
OCMStub([self.mockParser resultSet]).andReturn(resultSet);
id partialMockSUT = OCMPartialMock(self.sut);
OCMStub([partialMockSUT tableView]).andReturn(mockTableView);
[self.sut configureTableFooterView];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
OCMVerifyAll(mockTableView);
}
The line with NSRunLoop crashes with EXC_BAD_ACCESS when run as suite but runs fine alone!
You can make class wrapper around dispatch_async, and pass it as dependency. Also you can make fake wrapper, and pass it in tests. If you interested in, I can provide much more detailed explanation.

Monitoring Reachability with AFNetworking in unit test fails

In a project I am working I have implemented the HTTP Manager Reachability example.
When I run the actual app, it goes inside the block and from there to the switch:
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
In addition, when I call ...reachabilityManager] isReachable method returns true as expected.
The problem occurs when I try to unit test a class method I wrote that uses ...reachabilityManager] isReachable as a precondition - it returns false and what is weird that during a debug I have noticed that it doesn't go inside the above block, it skips it.
Of course, in the actual app it goes inside the block.
I have even tried to mock the class that implements the HTTP Manager Reachability example using OCMock in the unit test but it gave me the same result:
// NetworkClass implements the example
NetworkClass *networkClass = [[NetworkClass alloc] init];
id mockedNetworkClass = OCMPartialMock(networkClass);
// startNetworkMonitoring method implements the whole example above
[mockedNetworkClass startNetworkMonitoring];
// Giving enough time for AFNetworking to finish
[NSThread sleepForTimeInterval:60.0f];
EDIT1:
Looks like semaphore/XCTestExpectation won't help, the problem is in AFNetworkReachabilityManager::startMonitoring:
The only way that we could get the callback we want is inside startMonitoring method at dispatch_async(dispatch_get_main_queue(), ^{
callback(status);
But it runs outside the unit test even if we use semaphore/XCTestExpectation as mentioned.
Still looking for soultions..
EDIT2:
I was trying to follow the objc.io for Testing Asynchronous Code but it seems to be missing some code and some of the explanations are lacking of integration details.
I'd imagine that sleeping the thread is causing issues.
Try using the expectations API documented in Writing Tests of Asynchronous Operations.
Something along the lines of this should get you started (note this is more of a demonstration of the expectations API rather than a complete working test case):
- (void)testReachability {
XCTestExpectation *expectation = [self expectationWithDescription:#"Wait for reachability"];
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
...
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
// timed out waiting for reachability
}];
}

How can I test addObserver and removeObserver are invoked on nsnotificationcenter, using ocmock

in my base mock class:
- (void)tearDown
{
_mockApplication = nil;
self.observerMock = nil;
self.notificationCenterMock = nil;
}
where notificaitonCenterMock is just an id;
Then in my tests I do things like this:
self.notificationCenterMock = [OCMockObject partialMockForObject:[NSNotificationCenter defaultCenter]];
[(NSNotificationCenter *) [self.notificationCenterMock expect]
removeObserver:self.component
name:UIApplicationDidBecomeActiveNotification
object:nil];
Now.. if I run this code my unit tests will spuriously fail (i.e. only 60 of 370 will run on one run, 70 or 65 the next). Several of my unit tests will fail with the following errors:
OCPartialMockObject[NSNotificationCenter]: expected method was not invoked: removeObserver:
<VPBCAdComponent-0x17d43e0-384381847.515513: 0x17d43e0> name:#"UIApplicationDidBecomeActiveNotification" object:nil
Unknown.m:0: error: -[VPBCAdComponentTests testCleanUpAfterDisplayingClickthrough_adBrowser_delegateCallback] :
OCPartialMockObject[NSNotificationCenter]: expected method was not invoked: removeObserver:
<VPBCAdComponent-0x17d43e0-384381847.515513: 0x17d43e0> name:#"UIApplicationDidBecomeActiveNotification" object:nil
The tests will then be Terminated. I can clearly see that partially mocking the notification center causes problems for running the test suite.
The question is, what should I do? It'd be extremely nice to ensure that important things such as observers are set, and regression proof..
If you can avoid the partial mock in this case, do it. You should be able to use a standard mock or nice mock if you only want to test that observers are added and removed.
And if you can isolate just a few tests that will verify the observers are added and removed, then it shouldn't have such a ripple effect?
id mockCenter = [OCMockObject mockForClass:[NSNotificationCenter class]];
[[mockCenter expect] addObserver:observer options:UIApplicationDidBecomeActiveNotification context:nil];
// method on the Subject Under Test
[mockCenter verify];
I personaly use local mocks in such cases. A smaller mock scope ensures less interference with other parts of the application. More important in case of NSUserDefaults or other shared objects. The test pattern I use is the same.
- (void)testRegisterNofificaitonTest {
id ncMock = OCMClassMock([NSNotificationCenter class]);
OCMStub([ncMock defaultCenter]).andReturn(ncMock);
UIViewController *sut = [UIViewController new];
[[ncMock expect] addObserver:sut selector:#selector(doSomething:) name:#"NotificationName" object:nil];
[sut viewDidLoad]; //assuming viewDidLoad calls [[NSNotificationCenter defaultCenter] addObserver: ...
[ncMock verify];
[ncMock stopMocking];
}

OCMock in a block context

So I am attempting to throw together a simple test to verify that I am receiving frequency values from my audioController correctly.
In my view I am making a call like this to setup up a block callback:
- (void) registerVolumeCallback {
NSNumberBlock frequencyCallback = ^(NSNumber *frequency) {
self.currentFrequency = frequency;
};
self.audioController.frequencyCallback = frequencyCallback;
}
In my audio controller the frequency callback block is called with an nsnumber containing the frequency.
In my tests file I have the following:
- (void) testFrequencyAudioServiceCallbackActive {
OCMockObject *mockEqualizer = [OCMockObject partialMockForObject:self.testEqualizer];
[[[mockEqualizer stub] andCall:#selector(mockDidUpdateFrequency:)
onObject:self] setCurrentFrequency:[OCMArg any]];
[self.testEqualizer startAnimating];
[ mockEqualizer verify];
}
And:
- (void) mockDidUpdateFrequency: (NSNumber *) frequency {
GHAssertTrue((frequency!= nil), #"Audio Service is messing up");
}
Where test equalizer is an an instance of the aforementioned view. So Im trying to do some swizzling here. Problem is, mockDidUpdateFrequency is never called. I tried putting:
self.currentFrequency = frequency;
outside of the block, and the swizzling does happen and I do get a call to mockDidUpdateFrequency. I also tried:
- (void) registerVolumeCallback {
__block UIEqualizer *blockSafeSelf = self;
NSNumberBlock frequencyCallback = ^(NSNumber *frequency) {
blockSafeSelf.currentFrequency = frequency;
};
self.audioController.frequency = frequencyCallback;
}
No luck. Some weird instance stuff is going on here in the block context that I am not aware of. Anyone know whats happening?
You'll need to provide some more details for a definitive answer. For example, how is registerVolumeCallback invoked? And is frequencyCallback your own code, or a third-party API?
With what you've provided, I suspect that frequencyCallback is an asynchronous call. So even though startAnimating might create the condition where it will eventually be invoked, you immediately verify the mock before the callback has had a chance to be invoked. To get your test to do what you want as written, you need to understand what queue that block is executed on, and you need to give it a chance to execute.
If it's invoked asynchronously on the main queue, you can let the main run loop spin before calling verify:
[self.testEqualizer startAnimating];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
[mockEqualizer verify];
If it's invoked on a different queue, you have some different options, but it would help to have a clearer picture how your code is structured first.