简体   繁体   中英

Test Cell in a UITableViewCell XCTest

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?

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)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM