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()