Mirek
Mirek

Reputation: 500

How to unit test `viewDidLoad()`

What is everybody's favourite way of injecting dependencies when unit testing UIViewController.viewDidLoad() method on iOS?

Given my implementation is:

class MyViewController: UIViewController {

    var service: Service = Service()

    override func viewDidLoad() {
        super.viewDidLoad()
        service.load()
    }
}

And my test class is something along this:

class MyViewController Tests: XCTestCase {

     var vc: MyViewController!
     var serviceMock = ServiceMock()

    override func setUp() {
       super.setUp()
       let storyboard = UIStoryboard(name: "Main", bundle: nil)
       vc = storyboard.instantiateViewController(withIdentifier: "MyViewController") as! MyViewController
       vc.service = serviceMock
    }
}

func testThatServiceIsCalled() {

       XCTAssertTrue(serviceMock.loadCalled)
 }

The obvious problem here is that viewDidLoad() is called at the moment I instantiate the viewController and the test fails as the mock is not injected properly.

Any solution?

Upvotes: 2

Views: 4971

Answers (2)

Cristik
Cristik

Reputation: 32923

TL;DR You should not unit test UI-related methods like this.

Controllers and Views are not suitable for unit testing for the simple reason that their usually involves operations that get reflected on the screen, and thus are (very) hard to test. Snapshot testing or UI automation tests are a better candidate for these two components of MVC.

A well-written Controller doesn't contain business logic, it delegates the UI stuff to the View, and the business actions to its Model. With this in mind, it's the Model that should be the one that gets to be unit tested.

Now, if you really want to assert that the your service get's called with the proper methods, you could set your VC as root view controller, this should ensure the loadView method gets called, which should trigger the viewDidLoad one:

func testThatServiceIsCalled() {
    let window = UIWindow(frame:  UIScreen.main.bounds)
    window.makeKeyAndVisible()
    window.rootViewController = vc
    XCTAssertTrue(serviceMock.loadCalled)
}

Upvotes: 1

Joakim Danielson
Joakim Danielson

Reputation: 52108

Why do you need to use the storyboard to create the view controller?

Wouldn't this work?

func testThatServiceIsCalled() {
  let vc = MyViewController()
  vc.service = serviceMock
  vc.viewDidLoad()

  XCTAssertTrue(serviceMock.loadCalled)
}

Upvotes: 7

Related Questions