Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tests for custom UITableViewCell, cellForRowAtIndexPath crashes with nil outlets

I have a ViewController that contains a tableView. Since I need to keep the code well covered with tests, I need to write a test for [tableView:cellForRowAtIndexPath]

import UIKit

class MainViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var source = [
        Country(name: "USA", capital: "Washington, D.C."),
        Country(name: "Argentina", capital: "Buenos Aires"),
        Country(name: "Mexico", capital: "Mexico, D.F.")
    ]
    let cellIdentifier = NSStringFromClass(MainViewController.self)

    init() {
        super.init(nibName: "MainViewController", bundle: nil)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let nib = UINib(nibName: "CustomCell", bundle: nil)
        tableView?.registerNib(nib, forCellReuseIdentifier: cellIdentifier)
        tableView.dataSource = self
        }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return source.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CustomCell
        if let cell = cell {
            cell.countryLabel.text = source[indexPath.row].name
            cell.capitalLabel.text = source[indexPath.row].capital
            return cell
        }
        return CustomCell()
    }
}

struct Country {
    var name: String
    var capital: String
}

The tests look like the following:

import UIKit
import XCTest

class MainViewControllerTests: XCTestCase {

    var controller: MainViewController!

    override func setUp() {
        super.setUp()

        controller = MainViewController()
        controller.view.description
    }

    override func tearDown() {
        super.tearDown()
    }

    func testTableViewOutlet() {
        XCTAssertNotNil(controller.tableView)
    }

    func testTableViewCellForRowAtIndexPath() {
        let indexPath = NSIndexPath(forRow: 0, inSection: 0)

        let cell = controller.tableView(controller.tableView, cellForRowAtIndexPath: indexPath) as! CustomCell

        XCTAssertEqual(cell.countryLabel.text!, "USA")
        XCTAssertEqual(cell.capitalLabel.text!, "Washington, D.C.")
    }
}

I get the following error when running the tests:

Test Case '-[CustomTableViewCellTests.MainViewControllerTests testTableViewCellForRowAtIndexPath]' started.
fatal error: unexpectedly found nil while unwrapping an Optional value

This function has alluded tests for months in my production application since [dequeueReusableCellWithIdentifier] returns nil only when run from the tests. I have actually tried many different approaches I have read in StackOverflow, like:

  1. Creating the CustomCell when [dequeueReusableCellWithIdentifier] returns nil
  2. Loading the NIB for the CustomCell using a temporaryController and grabbing the view, which is supposed to be connected to the cell

The bottom line is that I get close to a solution, I create an CustomCell object, but the outlets for the cell remain nil. I haven't found the piece of code that would realize the outlets into initialized elements so I can make my tests pass.

I am including a basic iOS project with its own git which contains the code that I have included here. It is a UIViewController that contains UITableView which displays data using a custom UITableViewCell. I'm hoping a find a solution that realizes the custom cell with its outlets to make the test pass.

Git repo source: https://bitbucket.org/jallauca/customcell/src

To clone the git repo:

git clone https://bitbucket.org/jallauca/customcell.git 
like image 996
Jaime Allauca Avatar asked Dec 10 '25 19:12

Jaime Allauca


1 Answers

For me, (Swift 3) I had to call the cellForRow datasource cellForRowAt function instead of accessing straight from the tableView.cellForRow function

//this 
let cell = controller.tableView(controller.tableView, cellForRowAt: IndexPath(row: 0, section: 0))

//instead of this
let cell = controller.tableView.cellForRow(at: IndexPath(row: 0, section: 0))
like image 71
Maria Avatar answered Dec 13 '25 15:12

Maria