Test Driven Development using tableview
I will write essential test cases for normal table view with some data by using TDD.
TDD is a technique in which , we write the test case first and try to make it come true.
Let’s start the test. Create a view controller by name “KittenViewController” and make it first view controller.
Create unit test class by name “KittenUniTest”
Create first test and copy below code in that case
func test001_ControllerHasTableView() {
guard let controller = UIStoryboard(name: "Main", bundle: Bundle(for: KittenViewController.self)).instantiateInitialViewController() as? KittenViewController else {
return XCTFail("Could not instantiate ViewController from main storyboard")
}
controller.loadViewIfNeeded()
XCTAssertNotNil(controller.tableView, "Controller should have a tableview")
}
This test case (test001_ControllerHasTableView) is getting viewcontroller from main storyboard and finding is this is present or call guard.
controller.loadViewIfNeeded()
This line is very important. load View before using the controller and it will initialize the views of controller and make them usable else may be any issue of using between UI component and controller.
XCTAssertNotNil(controller.tableView, "Controller should have a tableview")
This will fail “Value of type ‘KittenViewController’ has no member ‘tableView’” because there is no variable by name tableview in controller.
To make this true, make Tableview in View controller and make IBOutlet
Now run it, its all green Hooray
Now we have controller that have a table view , Time to write the tableview datasource.
Datasource is responsible to provide and manipulate data in table view write below code for testing of data source.
func test002_TableViewDataSourceIsKittenDataSource() {
guard let controller = UIStoryboard(name: "Main", bundle: Bundle(for: KittenViewController.self)).instantiateInitialViewController() as? KittenViewController else {
return XCTFail("Could not instantiate ViewController from main storyboard")
}
controller.loadViewIfNeeded()
XCTAssertTrue(controller.tableView.dataSource is KittenDataSource,
"TableView's data source should be a KittenDataSource")
}
Test is fial
It will fail because controller doesn’t have any “KittenDataSource” For this , first create new Unit test by name “KittenDataSourceTests” and create first test in that class
func test001_DataSourceHasKittens() {
let dataSource = KittenDataSource()
XCTAssertEqual(dataSource.kittens.count, 20,
"DataSource should have correct number of kittens")
}
Run the test and it will fail because there is no “KittenDataSource” class. so create this class and just write in KittenDataSource
class KittenDataSource { }
Run the test, it will fail again with error “Value of type ‘KittenDataSource’ has no member ‘kittens’”
Create an array In kittendatasource class
var kittens = [Kitten]()
Run the test. Fails again with error “Use of unresolved identifier ‘Kitten’; did you mean ‘listen’?”
No we don’t want to listen :P so create class “Kitten” and copy below line
class Kitten { }
Fails again because kitten does not have any data and we are checking array of length 20. Time to add some kitten in data source to verify the test. Kitten have name and isAdoptable property.
Add below lines in test case
for number in 0..<20 {
let kitten = Kitten(name: "Kitten: \(number)")
dataSource.kittens.append(kitten)
}
Run it but it will still fail because kitten does not have name property. Add name property in kitten and initialize that. Open Kitten.swift file and copy below lines
var name: String
init(name: String) {
self.name = name
}
Run the test . It is green now .
Test number of rows
Now check whether tableview has the correct number of rows or not. For simplicity, make datasource as public variable and initialize that in after class. Also copy the above input data lines in set up for data And copy below lines for that
func test002_NumberOfRows() {
let tableView = UITableView()
let numberOfRows = dataSource.tableView(tableView, numberOfRowsInSection: 0)
XCTAssertEqual(numberOfRows, 20,
"Number of rows in table should match number of kittens")
}
Run the unit test , it will fail because “Value of type ‘KittenDataSource?’ has no member ‘tableView’” To resolve this open data source and inherit datasource class fromNSOBject and implement protocol of tableViewdatasource. Implement important function of tableViewdatasource
And add these functions in KittenDataSource. Kitendatasource file should be like this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return kittens.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
Run the Test code and it should be green
Test the number of displayed rows
Write another test case of tableRow. Number of rows to be displayed
func test002_NumberOfRows() {
let tableView = UITableView()
let numberOfRows = dataSource.tableView(tableView, numberOfRowsInSection: 0)
XCTAssertEqual(numberOfRows, 20,
"Number of rows in table should match number of kittens")
}
It should be passed by using above code
Test for cell of tableview
First let’s make tableview as global and then copy the below function to check whether label on tableview cell has data or not
func test003_CellForRow() {
let cell = dataSource.tableView(tableView, cellForRowAt: IndexPath(row: 0, section: 0))
XCTAssertEqual(cell.textLabel?.text, "Kitten: 0",
"The first cell should display name of first kitten")
}
Run the test, it will fail because it will not find any cell by name of “Cell” in tableview.
Open the tableView in storyboard and add a cell. Give it identifier. “Cell” and run the code.
It will still fails, because we haven’t register it before using.
Register in starting of cell by this line
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
Run the code. It will fail because kitten is not showing the name on cell.
Open the class of KittenDataSource and add this line in CellForRow
cell.textLabel?.text = kittens[indexPath.row].name
Run the test . It should be green
Test for name of kitten
We have tested that kitten should have name that will be set on kitten tableView class. To verify this, make a different unit test by name “KittenTests” Remove the boilerplate test and add below line in tests
func test001_KittenHasAName() {
let kitten = Kitten(name: "Uncle Fester")
XCTAssertEqual(kitten.name, "Uncle Fester", "Kitten name should be set in initializer")
}
Run the test and it should work
But there is a problem , we still have one failed case in “KittenUniTest”. That is “XCTAssertTrue failed - TableView’s data source should be a KittenDataSource” This is because we have tableview but still did not set its datasource.
Open storyboard and drag and drop datasource of tableview to controller
Now open kittenviewcontroller and assign datasource to tableview in setter method
@IBOutlet weak var tableView: UITableView! {
didSet {
tableView.dataSource = dataSource
}
}
And initialize kittendatasource on top
private var dataSource = KittenDataSource()
Run the test it should pass all
Test for adoption of kitten
Kitten either be adopted or not.. Check by default its not adopted ,Open KittenUniTest controller and write below line for testing
func test003_KittenIsNotAdoptableByDefault() {
let kitten = Kitten(name: "Uncle Fester")
XCTAssertFalse(kitten.isAdoptable,
"Kitten should not be adoptable by default")
}
Run the test it will fails on obvious reason. isAdoptable is not found, open kitten class and add in that this property.
var isAdoptable = false
Run test test it should pass
Its better time to global the controller and tableview variable In kitten data test general so that every test case use it.
Copy below Code in Setup() of KittenTesrcontroller
guard let vc = UIStoryboard(name: "Main", bundle: Bundle(for: KittenViewController.self)).instantiateInitialViewController() as? KittenViewController else {
return XCTFail("Could not instantiate ViewController from main storyboard")
}
controller = vc
controller.loadViewIfNeeded()
tableView = controller.tableView
guard let ds = tableView.dataSource as? KittenDataSource else {
return XCTFail("Controller's table view should have a kitten data source")
}
dataSource = ds
delegate = tableView.delegate
And initialize appropriate variables on top and remove the duplication of code from all test
Check for table view cell existance
Check whether table view has cells or not. Create another test class for hasCell
func test004_TableViewHasCells() {
let cell = controller.tableView.dequeueReusableCell(withIdentifier: "Cell")
XCTAssertNotNil(cell,
"TableView should be able to dequeue cell with identifier: 'Cell'")
}
Run the test , it should be green
Test disclosure indicator on adopted kitten
If it’s adoptable then show the disclosure indicator Copy the below code for Check if disclosure indicator is on if adoptable
func test005_KittenCellHasDisclosureIndicatorWhenAdoptable() {
guard let dataSource = controller.tableView.dataSource as? KittenDataSource else {
return XCTFail("Controller's table view should have a kitten data source")
}
let kitten = Kitten(name: "Adopt Me")
kitten.isAdoptable = true
dataSource.kittens.append(kitten)
let cell = dataSource.tableView(controller.tableView, cellForRowAt: IndexPath(row: 0, section: 0))
XCTAssertEqual(cell.accessoryType, .disclosureIndicator,
"Cell should have disclosure indicator for adoptable kitten")
Run the code ,I fail and error is “XCTAssertEqual failed: (“UITableViewCellAccessoryType”) is not equal to (“UITableViewCellAccessoryType”) - Cell should have disclosure indicator for adoptable kitten”
To resolve this ,open the storyboard and set accessory to “Disclosure indicator”
Run the test ,it should be green
Test hide disclosure indicator on non-adopted kitten
If its not adoptable then should not show disclosure indicator
functest006_KittenCellDoesNotHaveDisclosureIndicatorWhenNotAdoptable() {
guard let dataSource = controller.tableView.dataSource as? KittenDataSource else {
return XCTFail("Controller's table view should have a kitten data source")
}
let kitten = Kitten(name: "Cannot Adopt Me")
dataSource.kittens.append(kitten)
let cell = dataSource.tableView(controller.tableView, cellForRowAt: IndexPath(row: 0, section: 0))
XCTAssertEqual(cell.accessoryType, .none,
"Cell should have no indicator for a non-adoptable kitten")
}
Run the test but it will fail with error “XCTAssertEqual failed: (“UITableViewCellAccessoryType”) is not equal to (“UITableViewCellAccessoryType”) - Cell should have no indicator for a non-adoptable kitten”
Because we have added discourse indicator by default but to handle this, write on datasource file
cell.accessoryType = ( kittens[indexPath.row].isAdoptable) ? .disclosureIndicator : .none
Because we have to check whether its on or off
Run the test .it will be working now
Check for delegate confromences
If kitten has adoptable then it’s clickable else it’s not clickable . For this we will enable delegate of tableview because delegate has functions of “WillRowselect ”
func test007_TableViewDelegateIsViewController() {
XCTAssertTrue(controller.tableView.delegate === controller,
"Controller should be delegate for the table view")
}
Run the test. It will fail because “XCTAssertTrue failed - Controller should be delegate for the table view” To run the code, drag and drop the delegate of tableview Run the test and run the code. It will work
Check clickability on non-adoptable kitten
If kitten is not adoptable, cell should be not clickable. Write the below code for this
func test008_CannotSelectCellWhenNotAdoptable() {
let kitten = Kitten(name: "Cannot Adopt Me")
dataSource.kittens.append(kitten)
XCTAssertNil(delegate.tableView?(tableView, willSelectRowAt: IndexPath(row: 0, section: 0)),
"Delegate should not allow cell selection for non-adoptable kittens")
}
All Is green
Check clickability on adoptable kitten
If kitten is adoptable, cell should be clickable. Write the below code for this
func test009_CanSelectCellWhenAdoptable() {
let kitten = Kitten(name: "Adopt Me")
kitten.isAdoptable = true
dataSource.kittens.append(kitten)
XCTAssertEqual(delegate.tableView?(tableView, willSelectRowAt: IndexPath(row: 0, section: 0)), IndexPath(row: 0, section: 0),
"Delegate should allow cell selection for adoptable kittens")
}
It fails by reason “XCTAssertEqual failed: (“nil”) is not equal to (“Optional([0, 0])“) - Delegate should allow cell selection for adoptable kittens”
Because we have not made function for selection of tableview. Write below code in kittenViewController
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
return dataSource.kittens[indexPath.row].isAdoptable ? indexPath : nil
}
And implement in UITableViewDleegate in kitenViewController
Run the code. It should be all green
Test on Device
To test on device ,write below code in view controller of kittenViewController
for num in 0..<100 {
let kitten = Kitten(name: "Kitten\(num)")
kitten.isAdoptable = arc4random_uniform(50) % 3 == 0
dataSource.kittens.append(kitten)
}
tableView.reloadData()
This is all test driven development