Table view's `cellForRow(at:)` is `nil` in Unit Test

In Unit Test, I have a simple table view with a bunch of basic cells (cells with just a label). I would like to access the cell using `cellForRow(at:)`, so I can test thing like selecting and deselecting the row programmatically, but this `cellForRow` queiry always returns `nil`.


There is some discussion online that I should be using the data source's `tableView(_, cellForRowAt:)` instead. This is _not_ my intention. I only want to test the cell's visibility, test selecting and deselecting them. To test for visibility, `cellForRow(at:)` is the right function to use. Furthermore, the data source's `tableView(_, cellForRowAt:)` has no safeguard for out-of-range index access, while table view's `cellForRow(at:)` will gracefully return `nil` in this case, which I also wanted to test my table view controller on.


However, while I can always get a valid cell from `tableViewController.tableView(_:cellForRowAt:)`, I couldn't understand why I always get `nil` from `tableView.cellForRow(at:)`. I have verfied both the `tableView` and the `tableViewController` are not `nil` in my unit test, and I have also triggered view loading with:


_ = tableViewController.view


in `setUp()`. I also verified with `tableView.indexPathsForVisibleRows`, and the result _does_ include the index path I used for `cellForRow(at:)`.


When I queried my cell through LLDB and breakpoints, sometimes my cell would show up properly. Is it possible that I am missing things like asynchronous waiting since loading up cells visually may be done in a different thread? Am I supposed to add expectation waiting of some sort to wait until the cells are fully loaded up before I can access them with `cellForRow(at:)`, even through `tableView.indexPathsForVisibleRows` already returns the expected index paths.? I tried to set this up but I'm not sure how to override my table view controller's `init()`.


Here's my code in the Unit Test class.


import XCTest
@testable import TableViewTest
class TableViewTestTests: XCTestCase {
   
    private var appDelegate: AppDelegate!
    private var tableVC: TableViewController!
   
    override func setUp() {
        super.setUp()
        
        appDelegate = UIApplication.shared.delegate as! AppDelegate
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)        
        tableVC = storyboard.instantiateViewController(withIdentifier: "TableViewController") as! TableViewController
       
        // Trigger view load and viewDidLoad()
        _ = tableVC.view
       
    }
   
    override func tearDown() {
        
        super.tearDown()
    }
    func testGetFirstRow() {
        let tableView = tableVC.tableView!
        let indexPath0 = IndexPath(item: 0, section: 0)

        let cell0 = tableView.cellForRow(at: indexPath0)
       
        let visibleRows = tableView.indexPathsForVisibleRows
        XCTAssert(visibleRows != nil)   // PASSED
        XCTAssert(tableView.indexPathsForVisibleRows!.contains(indexPath0)) // PASSED
       
        XCTAssert(cell0 != nil)     // FAILED
    }
   
    func testGetFirstRowDataSource() {
        let tableView = tableVC.tableView!
        let indexPath0 = IndexPath(item: 0, section: 0)
       
        // This won't check for cell visibility.
        let cell0 = tableVC.tableView(tableView, cellForRowAt: indexPath0)
       
        let visibleRows = tableView.indexPathsForVisibleRows
        XCTAssert(visibleRows != nil)           // PASSED
        XCTAssert(tableView.indexPathsForVisibleRows!.contains(indexPath0))     // PASSED
    }
Post not yet marked as solved Up vote post of huapapojt Down vote post of huapapojt
3.9k views

Replies

Hello! Did you ever get an answer/solution to this problem? I'm facing a similar issue now. Thanks!

Guys, did you try reloading the table view altogether? I used table view over a view controller and it worked for me.

    func test_firstRow() {
        let tableView = myViewController().tableView
        // Reload data
        tableView?.reloadData()
        let indexPath0 = IndexPath(item: 0, section: 0)
        let cell0 = tableView!.cellForRow(at: indexPath0)
        let visibleRows = tableView?.indexPathsForVisibleRows
        XCTAssert(visibleRows != nil)
        XCTAssertTrue(((tableView?.indexPathsForVisibleRows!.contains(indexPath0)) != nil))
        XCTAssert(cell0 != nil)
    }

Same problem here! Even with the @ojshri answer I am not able to get any tableview rows, headers or footers. Does anyone knows if there is some other important thing to do in order to fix this?

PS: This is specific for Unit Tests, running the project on simulator everything it's okay!