iOS - Unit Testing Asynchoronous code - objective-c

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.

Related

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
}];
}

Test code with dispatch_async calls

Following TDD I'm developing an iPad app that downloads some info from the internet and displays it on a list, allowing the user to filter that list using a search bar.
I want to test that, as the user types in the search bar, the internal variable with the filter text is updated, the filtered list of items is updated, and finally the table view receives a "reloadData" message.
These are my tests:
- (void)testSutChangesFilterTextWhenSearchBarTextChanges
{
// given
sut.filterText = #"previous text";
// when
[sut searchBar:nil textDidChange:#"new text"];
// then
assertThat(sut.filterText, is(equalTo(#"new text")));
}
- (void)testSutReloadsTableViewDataAfterChangeFilterTextFromSearchBar
{
// given
sut.tableView = mock([UITableView class]);
// when
[sut searchBar:nil textDidChange:#"new text"];
// then
[verify(sut.tableView) reloadData];
}
NOTE: Changing the "filterText" property triggers right now the actual filtering process, which has been tested in other tests.
This works OK as my searchBar delegate code was written as follows:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.filterText = searchText;
[self.tableView reloadData];
}
The problem is that filtering this data is becoming a heavy process that right now is being done on the main thread, so during that time the UI is blocked.
Therefore, I thought of doing something like this:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredData = [self filteredDataWithText:searchText];
dispatch_async(dispatch_get_main_queue(), ^{
self.filteredData = filteredData;
[self.tableView reloadData];
});
});
}
So that the filtering process occurs in a different thread and when it has finished, the table is asked to reload its data.
The question is... how do I test these things inside dispatch_async calls?
Is there any elegant way of doing that other than time-based solutions? (like waiting for some time and expect that those tasks have finished, not very deterministic)
Or maybe I should put my code on a different way to make it more testable?
In case you need to know, I'm using OCMockito and OCHamcrest by Jon Reid.
Thanks in advance!!
There are two basic approaches. Either
Make things synchronous only while testing. Or,
Keep things asynchronous, but write an acceptance test that does resynchronizing.
To make things synchronous for testing only, extract the code that actually does work into their own methods. You already have -filteredDataWithText:. Here's another extraction:
- (void)updateTableWithFilteredData:(NSArray *)filteredData
{
self.filteredData = filteredData;
[self.tableView reloadData];
}
The real method that takes care of all the threading now looks like this:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredData = [self filteredDataWithText:searchText];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateTableWithFilteredData:filteredData];
});
});
}
Notice that underneath all that threading fanciness, it really just calls two methods. So now to pretend that all that threading was done, have your tests just invoke those two methods in order:
NSArray *filteredData = [self filteredDataWithText:searchText];
[self updateTableWithFilteredData:filteredData];
This does mean that -searchBar:textDidChange: won't be covered by unit tests. A single manual test can confirm that it's dispatching the right things.
If you really want an automated test on the delegate method, write an acceptance test that has its own run loop. See Pattern for unit testing async queue that calls main queue on completion. (But keep acceptance tests in a separate test target. They're too slow to include with unit tests.)
Albite Jons options are very good options most of the time, sometime it creates less cluttered code when doing the following. For example if your API has a lot small methods that are synchronised using a dispatch queue.
Have a function like this (it could be a method of your class as well).
void dispatch(dispatch_queue_t queue, void (^block)())
{
if(queue)
{
dispatch_async(queue, block);
}
else
{
block();
}
}
Then use this function to call the blocks in your API methods
- (void)anAPIMethod
{
dispatch(dispQueue, ^
{
// dispatched code here
});
}
You would usually initialise the queue in your init method.
#implementation MyAPI
{
dispatch_queue_t dispQueue;
}
- (instancetype)init
{
self = [super init];
if (self)
{
dispQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
Then have a private method like this, to set this queue to nil. It is not part of your interface, the API consumer will never see this.
- (void) disableGCD
{
dispQueue = nil;
}
In your test target you create a category to expose the GCD disabling method:
#interface TTBLocationBasedTrackStore (Testing)
- (void) disableGCD;
#end
You call this in your test setup and your blocks will be called directly.
The advantage in my eyes is debugging. When a test case involves a runloop so that blocks are actually called, the problem is that there has to be a timeout involved. This timeout is usually quite short because you don't want to have tests that last long if the they run into the timeout. But having a short timeout means your test runs into the timeout when debugging.

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.

overwriting a method via stubbing with OCMock

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

OCUnit test for protocols/callbacks/delegate in Objective-C

Using OCUnit, is there a way to test delegate protocols?
I'm trying this, which doesn't work.
-(void) testSomeObjDelegate {
SomeObj obj = [[SomeObj alloc] initWithDelegate:self];
[obj executeMethod];
}
-(void) someObjDelegateMethod {
//test something here
}
I'm going to try calling the obj method on a different thread and have the test sleep until the delegate is called. It just seems like there should be an easier way to test this.
Testing a delegate is trivial. Just set an ivar in the test in your callback method, and check it after what should be triggering the delegate callback.
For example, if I have a class Something that uses a delegate of protocol SomethingDelegate and sends that delegate -something:delegateInvoked: in response to some message, I can test it lik ethis:
#interface TestSomeBehavior : SenTestCase <SomethingDelegate>
{
Something *_object;
BOOL _callbackInvoked;
}
#end
#implementation TestSomeBehavior
- (void)setUp {
[super setUp];
_object = [[Something alloc] init];
_object.delegate = self;
}
- (void)tearDown {
_object.delegate = nil;
[_object release];
[super tearDown];
}
- (void)testSomeBehaviorCallingBack {
[_object doSomethingThatShouldCallBack];
STAssertTrue(_callbackInvoked,
#"Delegate should send -something:delegateInvoked:");
}
- (void)something:(Something *)something delegateInvoked:(BOOL)invoked {
_callbackInvoked = YES;
}
#end
I think you already understand this, however, from the way you've phrased your question. (I'm mostly posting this for other readers.) I think you're actually asking a more subtle question: How do I test something that may occur later such as something that spins the runloop. My cue is your mention of sleeping and threading.
First off, you should not just arbitrarily invoke a method on another thread. You should only do so if it's documented to be safe to use in that way. The reason is that you don't know what the internals of the class do. For example, it might schedule events on the run loop, in which case running the method on a different thread will make them happen on a different run loop. This would then screw up the class's internal state.
If you do need to test something that may take a little time to happen, you can do this just by running the current run loop. Here's how I might rewrite the individual test method above to do that:
- (void)testSomeBehaviorCallingBack {
NSDate *fiveSecondsFromNow = [NSDate dateWithTimeIntervalSinceNow:5.0];
[_object doSomethingThatShouldCallBack];
[[NSRunLoop currentRunLoop] runUntilDate:fiveSecondsFromNow];
STAssertTrue(_callbackInvoked,
#"Delegate should send -something:delegateInvoked:");
}
This will spin the current run loop in the default mode for 5 seconds, under the assumption that -doSomethingThatShouldCallBack will schedule its work on the main run loop in the default mode. This is usually OK because APIs that work this way often let you specify a run loop to use as well as a mode to run in. If you can do that, then you can use -[NSRunLoop runMode:beforeDate:] to run the run loop in just that mode instead, making it more likely that the work you're expecting to be done will be.
Please, review Unit Testing Asynchronous Network Access. I think can help you.
In short what it does is:
Add the following method which will take care of the synchronization
between the unit test code and the asynchronous code under test:
- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
} while (!done);
return done;
}