I'm using OCMockito and I want to test a method in my ViewController that uses a NetworkFetcher object and a block:
- (void)reloadTableViewContents
{
[self.networkFetcher fetchInfo:^(NSArray *result, BOOL success) {
if (success) {
self.model = result;
[self.tableView reloadData];
}
}];
}
In particular, I'd want to mock fetchInfo:
so that it returns a dummy result
array without hitting the network, and verify that the reloadData
method was invoked on the UITableView
and the model is what it should be.
As this code is asynchronous, I assume that I should somehow capture the block and invoke it manually from my tests.
How can I accomplish this?
(Edit: See Eugen's answer, and my comment. His use of OCMockito's MKTArgumentCaptor not only eliminates the need for the
FakeNetworkFetcher
, but results in a better test flow that reflects the actual flow. See my Edit note at the end.)Your real code is asynchronous only because of the real
networkFetcher
. Replace it with a fake. In this case, I'd use a hand-rolled fake instead of OCMockito:With this, you can create helper functions for your tests. I'm assuming your system under test is in the test fixture as an ivar named
sut
:Now your success path test needs to ensure that your table view is reloaded with the updated model. Here's a first, naive attempt:
Unfortunately, this doesn't guarantee that the model is updated before the
reloadData
message. But you'll want a different test anyway to ensure that the fetched result is represented in the table cells. This can be done by keeping the real UITableView and allowing the run loop to advance with this helper method:Finally, here's a test that's starting to look good to me:
But your real test will depend on how your cells actually represent the fetched results. And that shows that this test is fragile: if you decide to change the representation, then you have to go fix up a bunch of tests. So let's extract a helper assertion method:
With that, here's a test that uses our various helper methods to be expressive and pretty robust:
Note that I didn't have this end in my head when I started. I even made some false steps along the way which I haven't shown. But this shows how I try to iterate my way to test designs.
Edit: I see now that with my FakeNetworkFetcher, the block get executed in the middle of
reloadTableViewContents
— which doesn't reflect what will really happen when it's asynchronous. By shifting to capturing the block then invoking it according to Eugen's answer, the block will be executed afterreloadTableViewContents
completes. This is far better.