Implementing tests using a web service

In the previous chapter, we wrote a stub for CLGeocoder. Now, we will write a test that asserts that the geocoder built into CoreLocation works as we expect it to. The fetching of coordinates from a geocoder is asynchronous. This means that we have to write a test that can deal with asynchronous interfaces.

Let's first structure the files a bit in the Project Navigator of Xcode. Select all the controller files in the main target (ItemListViewController.swift, ItemListDataProvider.swift, ItemCell.swift, DetailViewController.swift, and InputViewController.swift), and press ctrl + click to create a group from the selection. Let's call this group Controller. Do the same with the corresponding test cases in the test target.

Now, let's get started with the test. We start naively by adding the following test to InputViewControllerTests:

func test_Geocoder_FetchesCoordinates() {


let address = "Infinite Loop 1, Cupertino"
CLGeocoder()
.geocodeAddressString(address) {
(placemarks, error) -> Void in


let coordinate =
placemarks?.first?.location?.coordinate
guard let latitude =
coordinate?.latitude else {


XCTFail()
return
}


guard let longitude =
coordinate?.longitude else {
XCTFail()
return
}


XCTAssertEqual(latitude,
37.3316,
accuracy: 0.001)
XCTAssertEqual(longitude,
-122.0300,
accuracy: 0.001)
}
}

Run the tests. All the tests pass. So, it looks like that the geocoder works as we thought it would. But wait a minute. We have skipped the red phase. In TDD, we first have to have a failing test. Otherwise, we cannot be sure whether the test actually works.

We have no access to the source of CLGeocoder, so we cannot change its implementation to make the test fail. The only thing we can do is to change the assertion. Replace the assertions within the closure with this code:

XCTAssertEqual(latitude,
0.0,
accuracy: 0.001)
XCTAssertEqual(longitude,
0.0,
accuracy: 0.001)

Run the tests again. Uh, the tests still pass. To figure out what is going on, add a breakpoint in the line of the first assertion:

Run the tests again. During the execution of this test, the debugger should stop at this line, so open the debugger console to investigate what is going on.

The debugger never reaches the breakpoint.

The reason for this is that geocodeAddressString(_:completionHandler:) call is asynchronous. This means that the closure is called sometime in the future on a different thread, and the execution of the tests moves on. The test is finished before the callback block is executed, and the assertions never get called. We need to change the test to make it asynchronous.

Replace test_Geocoder_FetchesCoordinates() with the following lines of code:

func test_Geocoder_FetchesCoordinates() {

let geocoderAnswered = expectation(description: "Geocoder")


let address = "Infinite Loop 1, Cupertino"
CLGeocoder()
.geocodeAddressString(address) {
(placemarks, error) -> Void in


let coordinate =
placemarks?.first?.location?.coordinate
guard let latitude =
coordinate?.latitude else {


XCTFail()
return
}


guard let longitude =
coordinate?.longitude else {


XCTFail()
return
}

XCTAssertEqual(latitude,
0.0,
accuracy: 0.001)
XCTAssertEqual(longitude,
0.0,
accuracy: 0.001)


geocoderAnswered.fulfill()
}


waitForExpectations(timeout: 3,
handler: nil)
}

The new lines are highlighted. We create an expectation using expectation(description:). At the end of the test, we call waitForExpectations(timeout:handler:) with a timeout of 3 seconds. This tells the test runner that it should stop at this point and wait until either all the expectations that are created in the test are fulfilled, or the timeout duration is over. If all the expectations are not fulfilled when the timeout duration has passed, the test fails. In the callback closure, we fulfill the expectation after the assertions are called.

Now, run the tests again. The last test fails because the coordinate we get from the geocoder does not match the values (0.0 and 0.0) we put into the assertions. Replace the assertions again with the correct ones that we had when we first wrote the test:

XCTAssertEqual(latitude,
37.3316,
accuracy: 0.001)
XCTAssertEqual(longitude,
-122.0300,
accuracy: 0.001)

Run the tests again. All the tests pass, and CLGeocoder works as expected.

We have just taken a look at how we can use XCTest to test asynchronous APIs. This can be used to test many different aspects of iOS development (for example, sending NSNotifications, fetching data from a web server, writing data to a database in the background, and so on). Whenever something asynchronous takes place, we can add expectations and set them as fulfilled when the asynchronous callback is executed.

This is very powerful. But keep in mind that unit tests should be fast and reliable. Using a web service in your tests makes the test fragile and slow. If the web server needs more than three seconds that we set as timeout, the test will fail. And you always need internet connectivity to run this test.

In the following sections, we will use stubs to make an asynchronous test robust and fast. The additional benefit is that we can develop the network layer of our app without a finished web server at hand. The only thing we need is a finished API documentation.

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

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