Conducting the first tests

Open Project Navigator and select the ToDoTests group. Add a new Unit Test Case Class and call it ItemListDataProviderTests. Add the @testable import ToDo import statement and remove the two test template methods.

The table view should have two sections--one for unchecked to-do items and the other for checked items. Add the following test to ItemListDataProviderTests:

func test_NumberOfSections_IsTwo() { 
  let sut = ItemListDataProvider() 
  let tableView = UITableView() 
  tableView.dataSource = sut 
   

  let numberOfSections = tableView.numberOfSections 
  XCTAssertEqual(numberOfSections, 2) 
} 

First, we create an instance of ItemListDataProvider, set up the table view, and then we check whether the table view has the expected number of sections. This test fails because the default number of sections for a table view is one. Open ItemListDataProvider and add the following code:

func numberOfSections(
in tableView: UITableView) -> Int {

return 2 }

This is enough to make all the tests pass again.

The number of rows in the first section should be the same as the number of to-do items. But where do we get the to-do items from? ItemListDataProvider needs a property of the type ItemManager to ask it for the items to present in the table view. Add the following code to ItemListDataProviderTests:

func test_NumberOfRows_Section1_IsToDoCount() { 
  let sut = ItemListDataProvider() 
  let tableView = UITableView() 
  tableView.dataSource = sut 
  
 
  sut.itemManager?.add(ToDoItem(title: "Foo")) 
}

At this point, we have to stop writing this test because the static analyzer complains 'ItemListDataProvider' has no member 'itemManager'. Open ItemListDataProvider and add the property var itemManager: ItemManager?. This makes the test compilable again. Add the following code at the end of test_NumberOfRows_InFirstSection_IsToDoCount():

XCTAssertEqual(tableView.numberOfRows(inSection: 0), 1) 
 
sut.itemManager?.add(ToDoItem(title: "Bar")) 

XCTAssertEqual(tableView.numberOfRows(inSection: 0), 2) 

First, we check whether the number of rows in the first section is equal to one after we have added an item to the item manager. Then, we add another item and check whether the number of rows is equal to two. Run the test. This test fails because the number of rows in the table view is always zero, as we have not implemented the corresponding data source method to return the correct values. Open ItemListDataProvider and replace tableView(_:numberOfRowsInSection:) with the following code:

func tableView(_ tableView: UITableView, 
               numberOfRowsInSection section: Int) -> Int { 
 
  
  return itemManager?.toDoCount ?? 0 
} 

This implementation returns the number of to-do items from itemManager if itemManager is not nil; otherwise, it returns zero. Run the tests. Oh, they still fail because the number of rows in the first section is always zero.

The reason for this is that the property required to hold a reference to the item manager is optional, and we never set a value for this property. Therefore, the value of itemManager is always nil, and the number of rows returned from the data source method is always zero.

At this point, it is not clear who is going to set the item manager to itemManager. We will decide this in a later chapter when we put all the modules together to form a complete app. For the tests, we will set itemManager in them. Add the following line right after let sut = ItemListDataProvider() in test_NumberOfRows_InFirstSection_IsToDoCount():

sut.itemManager = ItemManager()

Run the tests. Now the first assertion passes but the second one, asserting that the number of rows is two after we added another item, fails. The reason for this is that table views seem to cache the values returned from tableView(_:numberOfRowsInSection:). This is one of the many performance optimizations that are built into table views. We, as developers, need to tell the table view that the data source has changed by calling reloadData(). Add the following code right after the line where the second to-do item is added to the item manager:

tableView.reloadData() 

Run the tests. All the tests pass. Before we move on, let's check whether there is anything to refactor. The implementation code looks nice and clean now, but the tests show some duplication. To refactor, let's first add two properties to ItemListDataProviderTests:

var sut: ItemListDataProvider! 
var tableView: UITableView! 

Then, add the following setup code to setUp():

sut = ItemListDataProvider() 
sut.itemManager = ItemManager() 
 

tableView = UITableView() 
tableView.dataSource = sut 

Finally, remove the following code from the test methods because it is no longer needed:

let sut = ItemListDataProvider() 
sut.itemManager = ItemManager() 
 
let tableView = UITableView() 
tableView.dataSource = sut 

Run the tests again to make sure that everything still works.

If the user checks an item in the first section, it should appear in the second section. Add the following test to make sure the number of rows in the second section is the same as the number of completed items in the item manager:

func test_NumberOfRows_Section2_IsToDoneCount() { 
  sut.itemManager?.add(ToDoItem(title: "Foo")) 
  sut.itemManager?.add(ToDoItem(title: "Bar")) 
  sut.itemManager?.checkItem(at: 0) 
  
 
  XCTAssertEqual(tableView.numberOfRows(inSection: 1), 1) 

   
  sut.itemManager?.checkItem(at: 0) 
  tableView.reloadData() 

   
  XCTAssertEqual(tableView.numberOfRows(inSection: 1), 2) 
} 

This test is similar to the earlier test. First, we add items to the item manager, and then we check an item and see whether the number of rows in the second section matches our expectations. Run the test. The test fails but look closely: the first assertion passes. This is because the implementation of tableView(_:numberOfRowsInSection:) returns the number of to-do items, and when the first assertion is called this is the same as the expected number of done items. This example shows that it is important to start with a failing test, otherwise, we cannot be sure we are testing the real thing. So, remove the second assertion and make the test red by replacing tableView(_:numberOfRowsInSection:) with the following code:

func tableView(_ tableView: UITableView, 
               numberOfRowsInSection section: Int) -> Int { 
   

  let numberOfRows: Int 
  switch section { 
  case 0: 
    numberOfRows = itemManager?.toDoCount ?? 0 
  case 1: 
    numberOfRows = 0 
  default: 
    numberOfRows = 0 
  } 
  return numberOfRows 
} 

Run the tests. Now, the assertion fails because the number of rows in the second section is always zero. To make the test pass, replace the assignment in case 1 with the following line of code:

numberOfRows = 1 

Run the tests again. The tests pass. Now, add the XCTAssertEqual(tableView.numberOfRows(inSection: 1), 2) assertion at the end of test_NumberOfRows_InSecondSection_IsToDoneCount() again.The test fails again. This is a good thing, however, because it means that we are actually testing whether the number of rows represents the number of items in the item manager. Replace the assignment in case 1 one more time with the following line of code:

numberOfRows = itemManager?.doneCount ?? 0

Run the tests again. All the tests pass. Let's now check whether there is something to refactor; indeed, there is. The implementation does not look good. There is a question mark at the end of itemManager, and in the switch statement, we need to implement the default case even though we know that there will never be more than two sections.

To improve the code, we start by adding an enum for the sections. Add the following code in ItemListDataProvider.swift but outside the ItemListDataProvider class:

enum Section: Int { 
  case toDo 
  case done 
} 

Now, replace the implementation of tableView(_:numberOfRowsInSection:) with the following code:

func tableView(_ tableView: UITableView, 
               numberOfRowsInSection section: Int) -> Int { 
   

  guard let itemManager = itemManager else { return 0 } 
  guard let itemSection = Section(rawValue: section) else { 
    fatalError() 
  } 
 
  
  let numberOfRows: Int 
   

  switch itemSection { 
  case .toDo: 
    numberOfRows = itemManager.toDoCount 
  case .done: 
    numberOfRows = itemManager.doneCount 
  } 
  return numberOfRows 
} 

This looks much better. We first check whether itemManager is nil using guard and return zero if this is the case. Then, we create itemSection from the argument section. The guard statement makes it clear that a value for the section argument can only be 0 or 1 because the Section enum only has two cases.

Run the tests to make sure that everything still works.

The to-do items should be presented in the table view using a custom table view cell because the cells provided by UIKit can only show an image and two text strings. In our case, we need to show three text strings because we want to show the title, location, and the due date.

Add the following test to make sure that tableView(_:cellForRowAt:) returns our custom cell:

func test_CellForRow_ReturnsItemCell() { 
  sut.itemManager?.add(ToDoItem(title: "Foo")) 
  tableView.reloadData() 
   

  let cell = tableView.cellForRow(at: IndexPath(row: 0,  
                                                section: 0)) 
   

  XCTAssertTrue(cell is ItemCell) 
} 

Xcode complains that ItemCell is an undeclared type. Open Project Navigator, add an iOS | Source | Cocoa Touch Class, and call it ItemCell. Make it a subclass of UITableViewCell. Store it in the Controller folder, and ensure that it is added to the ToDo target and not to the ToDoTests target. Remove all the template code, such that the file looks as follows:

import UIKit 
 

class ItemCell: UITableViewCell { 
} 

Now, the test compiles but still fails. Replace the return statement in tableView(_:cellForRowAt:) with the following line of code:

return ItemCell() 

This change is enough to make the tests pass, but it is clearly not enough for the feature that we want to implement. For performance reasons, table view cells need to be dequeued. Before we can write a test that makes sure that a cell is dequeued, we need to introduce a very important concept in unit testing--fake objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.63.114