Reputation: 1479
I'm testing a UITableView where the view controller is
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomTableViewCell")
}
var data = [1,2,3,4,5,6,7]
}
extension ViewController : UITableViewDelegate {
}
extension ViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath)
cell.textLabel?.text = data[indexPath.row].description
return cell
}
}
with the test
func testViewCell() {
guard let controller = controller else {
return XCTFail("Could not instantiate ViewController")
}
let tableCell = Bundle(for: CustomTableViewCell.self).loadNibNamed("CustomTableViewCell", owner: nil)?.first as! CustomTableViewCell
tableCell.textLabel?.text = "2"
controller.loadViewIfNeeded()
let actualCell = controller.tableView!.cellForRow(at: IndexPath(row: 0, section: 0) )
XCTAssertEqual(actualCell, tableCell)}
However I get the cell as nil.
This is surprising since a breakpoint in the view controller indicates that the cell is being allocated, so there is something wrong in the line
let actualCell = controller.tableView!.cellForRow(at: IndexPath(row: 0, section: 0) )
So how can I test the contents of this cell?
Upvotes: 2
Views: 3539
Reputation: 20980
Problem 1: If you define your cell in a nib, you need to register that nib (instead of the type).
tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
Problem 2:
It seems directly calling controller.tableView!.cellForRow(at:)
can return nil
. But this isn't how UIKit calls your table view. Instead, call it through its data source. Let's have the test do the same:
let actualCell = controller.tableView.dataSource?.tableView(controller.tableView, cellForRowAt: IndexPath(row: 0, section: 0))
This now returns a cell, and the assertion fails saying the two instances of CustomTableViewCell aren't equal.
Bonus: If you ever want to move the data source into a separate class, you can do so without changing the test. The test doesn't know or care who implements the data source.
Problem 3:
Changing the test to set "1"
as the text label of the expected cell still doesn't pass. This may be because each cell has its own layer
. So instead of setting up an expected cell, cast the actual cell to a CustomTableViewCell. Then you can inspect its properties.
guard let cell = actualCell as? CustomTableViewCell else {
XCTFail("Expected \(CustomTableViewCell.self), but was \(actualCell)")
return
}
XCTAssertEqual(cell.textLabel?.text, "1")
Improvement: Going through the table view's data source, and passing it the table view as the first argument is awkward. We can make it easier to read and write table view tests by defining standalone helper functions.
func cellForRow(in tableView: UITableView, row: Int, section: Int = 0) -> UITableViewCell? {
return tableView.dataSource?.tableView(tableView, cellForRowAt: IndexPath(row: row, section: section))
}
Using this helper, we can simply write:
let actualCell = cellForRow(in: controller.tableView row: 0)
Upvotes: 6
Reputation: 149
Step 1: Go to Identity Inspector of your view controller in storyboard and give it a storyboard id. Ex: "vcIdentifier"
Step 2: You have get the instance of your storyboard in Unit test
Step 3 : Instantiate your view controller via storyboard instance from step 2
Step 4 : Get access to your table view from the viewcontroller and pass it to 'cellForRowAt' method in your test. Please see below a sample code:
let storyboard = UIStoryboard(name: "Main", bundle:nil)
let newsViewController: UITableViewController = storyboard.instantiateViewController(identifier: "vcIdentifier")
return newsViewController.tableView // Use this tableview to call 'cellforRow'
Step 5: Remove any tableview.register() method added in your test code. If you register cell method, all the outlets will be lost.
Upvotes: 1