Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing ViewController's Lifecycle

I am writing unit test for my ViewControllers. What is the best practice for methods such as viewDidAppear:, viewWillAppear: , etc.

I am using [vc view] which calls viewDidLoad. But other than viewDidLoad, how should I unit test other lifecycle methods. Is it common to have them unit tested at all? Does that make sense to call them directly? like:

[vc viewWillAppear:NO];
[vc viewDidAppear:NO];

Thanks

like image 831
ramo Avatar asked Feb 26 '15 20:02

ramo


2 Answers

There are a lot of developers who either avoid or don't believe you can test UIViewControllers. Apples poor testing doco doesn't help with this either.

UIViewControllers are quite testable with a number of approaches that you can take.

Firstly, as anyone will tell you, try and keep business logic out of your view controller. Try to make it just about load the view and as little else as possible. Changing from MVC architecture to something else may help with this. Depending on what you are building, you may also want to consider using custom UIView classes to help as well.

Pure unit/logic tests

I you are dealing with what Apple call 'Logic test', i.e. a test target with no executable assigned. You can still test a lot. Methods that contain simple view handling code can be tested by manually setting a view, or using the test code to manually load a xib file etc. Mocking frameworks such as OCMock can be very useful as well.

Often though, in these sorts of tests, you will end up needed to manually can various lifecycle methods to execute the code you want to test. For example, if you are wanting to test a viewDidLoad method:

id mockView = OCMClassMock([UIView class]);
// Setup mock expectations.
myViewController.view = mockView;
[myViewController viewDidLoad];

Application tests

If you are using a test target where you have set the application, then you can actually do some tests from with a unit test without trying to navigate the app. This is a bit of a cheat, but I've found it quite useful at times.

UIView *myView = // ... load the view manually or simply [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)]  
myViewController.view = myView;
[[UIApplication sharedApplication].keyWindow addSubview:myView];

Be sure to remove the view in the teardown if you take this approach. The advantage of doing this is that all lifecycle methods around getting a view on screen are automatically called for you.

Mostly I've found this useful for test stuff in relation to view controllers and custom views that relate to views which are parts of larger screen layouts. Not so much top level view controllers.

UI testing

Finally there is the new UI testing framework that Apple has supplied. I've used 3rd party Ruby frameworks such as Frank and Calabash in the past. As it turns out, Apples UI Testing is actually quite good and pretty comparable to these tools.

The trick with it is to use it to build up a library of methods that make sense and help describe (using a DSL) various aspects of your app.

The downside of this approach is that you cannot just load up the view you want to test out. You have to actually run the app and navigate to it. The other major downside of this is that it is very much based on the external accessibility based view of your app. It's almost impossible to get to the internals so testing is based on the apps on screen behaviour rather than internal classes.

As yet I've not explored the idea of mixing this form of testing and manually loading views onto the window, but I cannot see why that would not work.

like image 64
drekka Avatar answered Sep 21 '22 16:09

drekka


You've just felt one big con of iOS ViewControllers: they suck at testability.

Another big problem with MVC is that it discourages developers from writing unit tests. Since view controllers mix view manipulation logic with business logic, separating out those components for the sake of unit testing becomes a herculean task. A task that many ignore in favour of… just not testing anything.

source

Do not test UIKit! You should test your own logic. Putting logic in your VC makes it very hard to test. Try to put it somewhere else and unit test it separately. The answer is: you should avoid testing by calling UIKit methods - especially that these methods were not designed to be called directly!

If you get all of your logic out of the ViewController, it'll really become a view: a dumb class that shows something on the screen. There should be nothing to test there.

Maybe you should think about using MVVM/MVP/VIPER instead. Please read the link provided, it will explain it all.

like image 29
michal.ciurus Avatar answered Sep 18 '22 16:09

michal.ciurus