When I started testing iOS apps, which shamefully was not when I started writing iOS apps, I discovered the biggest impediment to thorough testing on iOS was the View Controller, and it’s mix of UI code, and not.
Now I’m working on my first independent app (yay!) of course I am writing extensive unit tests.
I won’t go into mocking here, but you need a mocking framework and some understanding of what mocking is for this to make sense. Currently, I’m using OCMock. Also, XCTest is not the best documented, and here is a handy list of asserts.
Introducing The Presenter
Step 1 for writing thorough unit tests is getting all the non-view code out of the ViewController. This goes in the Presenter. So for each View Controller, I know have MyClassViewController and MyClassPresenter.
Over time I have refined this and now I have at a top level “ViewController” and “Presenter” classes. The Presenter knows what ViewController it has, but the MyClassViewController knows nothing, the ViewController merely knows there is a Presenter, and can call some standard methods – viewLoaded:, leftNavigationButtons:, rightNavigationButtons:.
This could be a blog post all on it’s own (maybe it will be soon!) but the point is: get non-view code out of the ViewController.
Perform Selector
This is handy for testing that the right thing happens when a button is pressed. This can be done using performSelector:
For example, if we want to verify that the first and only left navigation button does what we want it to:
// Extract the button. UIBarButtonItem *button = (UIBarButtonItem *) [[presenter_ leftNavigationButtons] firstObject]; // Perform the action. [[button target] performSelector:[button action] withObject:button];
Then, verify.
Partial Mock For Object – Woah
I discovered this via StackOverflow and it’s genius. One thing that I want to mock and verify when testing my ViewController is the navigationController property. But it’s up in the super class, and the trick of setValue:forKey: was not working.
So what you can do, is create a partialMockForClass, and then stub the navigationController property. (Here, I’m making sure that the ViewController dismiss… method for use by the presenter, causes the navigationController method to be called)
// Create the mock navigation controller. id mockNavigationController = OCMStrictClassMock([UINavigationController class]); // Create a partial mock for the ViewController. id mockViewController = [OCMockObject partialMockForObject:viewController_]; // Stub to return mockNavigationController. [[[mockViewController expect] andReturn:mockNavigationController] navigationController]; // Set up expectations. OCMExpect([mockNavigationController dismissViewControllerAnimated:YES completion:nil]); // Call the method that should trigger them. [viewController_ dismissViewControllerAnimated:YES withCompletionBlock:nil]; // Verify! OCMVerify([mockNavigationController dismissViewControllerAnimated:YES completion:nil]);
That’s it!
♥ Happy Unit Testing ♥
For more detail on this and other aspects of iOS unit-testing you might find my digital workshop helpful.
5 replies on “Better Testing of View Controllers on iOS”
I told y’all I would go back to tweeting about unit testing – Better Testing of View Controllers on iOS – http://t.co/b71nM85wQ3
RT @catehstn: I told y’all I would go back to tweeting about unit testing – Better Testing of View Controllers on iOS – http://t.co/b71nM85…
[…] Better Testing of View Controllers on iOS […]
[WORDPRESS HASHCASH] The comment’s server IP (69.163.242.185) doesn’t match the comment’s URL host IP (69.163.242.203) and so is spam.
[…] I previously wrote about better testing of view controllers on iOS I alluded briefly to the strategy of breaking the ViewController into a ViewController and a […]
[WORDPRESS HASHCASH] The comment’s server IP (69.163.242.185) doesn’t match the comment’s URL host IP (69.163.242.203) and so is spam.
[…] Alternative: we write tests to return real UIButtons, then we can tap it and verify what happens. I’ve covered how in more detail here. […]
[WORDPRESS HASHCASH] The comment’s server IP (69.163.242.185) doesn’t match the comment’s URL host IP (69.163.242.203) and so is spam.