Home > front end >  How to create a mock view controller from storyboard
How to create a mock view controller from storyboard

Time:06-07

I'm trying to create a mock view controller for a view controller that was created from a storyboard.

If I try to downcast it as following, it doesn't work since the storyboard view controller is encoded:

let _storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = _storyboard.instantiateViewController(ofType: ViewController.self) as? MockViewController

where MockViewController is the subclass of ViewController.

I tried initializing the mock view controller using the nib name, but the nil error happens for the uninitialized @IBOutlet properties:

class MockViewController: ViewController {
    init() {
        let controllerName = String(describing: ViewController.self)
        super.init(nibName: controllerName, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

I could potentially create an identical view controller and initialize them manually:

let _storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = _storyboard.instantiateViewController(ofType: ViewController.self)
let mockViewController = MockViewController()
mockViewController.someProperty = vc.property

but doesn't seem very efficient and also it entails always manually coordinating changes in both the production code and the test code.

The most promising method I found was using this initializer:

let controllerName = String(describing: ViewController.self)
let mockVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: controllerName, creator: { coder -> MockViewController? in
    return MockViewController(coder: coder)
})

where the MockViewController is the subclass of ViewController again and has the following initializer:

required init?(coder: NSCoder) {
    super.init(coder: coder)
}

However, this also results in the same error as the first one where the @IBOutlet properties return nil errors.

CodePudding user response:

You're trying to use "Subclass and Override" to do partial mocking, so that you can record calls to the view controller. But this isn't possible on a view controller defined in a storyboard. As I write in my book,

Storyboard-based view controller can't be subclassed because the storyboard stores an instance of a predetermined type.

Alternatives:

  • Load your storyboard from a XIB instead of a Storyboard.
  • Define your UI programatticaly instead of in a Storyboard.
  • Have your Presenter use a protocol instead of a direct reference to your view controller. Then inject what you like.

CodePudding user response:

In the last scenario , is :

let controllerName = String(describing: ViewController.self)
let mockVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: controllerName, creator:
{ coder -> MockViewController? in
    return MockViewController(coder: coder)
})

Before try to access to outlets , you need to load the outlets of the page so below code could solve your problem. Just add it after mockVc definition

mockVC.loadView()
  • Related