I am trying to test an subclass of UITableViewController
in swift with storyboard. I am able to get a reference to the view controller and log it, but I am unable to typecast it to the class I am trying to test, so I can't access methods, properties etc.
What's the proper way of getting a hold of the instance of my class?
import XCTest
import UIKit
class GameListControllerTest: XCTestCase {
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var _sut: AnyObject?
var sut: AnyObject {
if(_sut?) {
return _sut!
}
_sut = storyboard.instantiateViewControllerWithIdentifier("GameListController")
return _sut!
}
override func setUp() {
super.setUp()
println()
println("=================================")
println("sut \(sut)")
println("view \(sut.view)")
println("=================================")
}
func testInstance() {
var vc1 = sut as UITableViewController
var vc2 = sut as? GameListController
println()
println("=================================")
println("VC1 \(vc1)")
println("VC2 \(vc2)")
println("=================================")
}
}
XCTestOutputBarrierTest Suite '_TtC15mytabletopTests22GameListControllerTest' started at 2014-06-16 06:13:30 +0000
XCTestOutputBarrierTest Case '-[_TtC15mytabletopTests22GameListControllerTest testInstance]' started.
XCTestOutputBarrier
=================================
sut <_TtC10mytabletop18GameListController: 0xb338d50>
view <UITableView: 0xc033800; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0xb344960>; layer = <CALayer: 0xb33c7d0>; contentOffset: {0, 0}; contentSize: {480, 0}>
=================================
=================================
VC1 <_TtC10mytabletop18GameListController: 0xb338d50>
VC2 nil
=================================
Test Case '-[_TtC15mytabletopTests22GameListControllerTest testInstance]' passed (0.007 seconds).
XCTestOutputBarrierTest Suite '_TtC15mytabletopTests22GameListControllerTest' passed at 2014-06-16 06:13:30 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 0.007 (0.010) seconds
If I try to force typecast (sut as GameListController
) I get an runtime exception.
Here is what's happening. It took me days to figure out, but my problem was on how my classes were being exported to the targets:
This was causing two binary copies of my class, one in the app target and another in the test target. If we pay more attention to the logs we might notice:
Test Case '-[_TtC15mytabletopTests22GameListControllerTest testInstance]'
The above is the test method testInstance
and it's part of mytabletopTests
execution context. Now lets take a look at the instance pulled from Storyboard:
sut <_TtC10mytabletop18GameListController: 0xb338d50>
This in turn is running on the mytabletop
context. This explains why the tests are unable to typecast to GameListController
. The GameListController
that it knows of is the one compiled inside the test target.
Since removing the class from the test target makes the class unknown to my test case, now I need to import my app target into the test case:
import XCTest
import UIKit
import mytabletop // LINE ADDED
class GameListControllerTest: XCTestCase {
Now, the only GameListController
that the test have access to is the same one the storyboard is instantiating, and I am finally able to type cast it. Here is the new testcase:
import XCTest
import UIKit
import mytabletop
class GameListControllerTest: XCTestCase {
let sut: GameListController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("GameListController") as GameListController
override func setUp() {
super.setUp()
UIApplication.sharedApplication().keyWindow.rootViewController = sut
XCTAssertNotNil(sut.view)
}
func testInstance() {
XCTAssertNotNil(sut)
XCTAssertNotNil(sut.tableView) // UITableViewController property
XCTAssertNotNil(sut.store) // instance property
XCTAssertNotNil(sut.someButton) // outlet
}
}
Now I am able to typecast correctly (scroll to see as GameListController
) during the instance initialization. In order to force all outlets to be bound correctly and subviews to render accordingly to the device the test is being run, we can make the view controller the rootViewController
for the app and pull the view from it, as seen in the setUp
function above. Even myCustomOutlet
is now working correctly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With