iOS: Writing test cases & Restructuring to testable code

Strategies for testing networking code in your app

iOS: Writing test cases & Restructuring to testable code

Today, I want to share four simple techniques with you that I found really helpful while writing tests for our app.

I am really excited to share some great testing techniques with you that I have been learning recently. As the WWDC conference was finished, I thought it would be really cool to explain things which are discussed in Testing Tips & Tricks WWDC video. In the WWDC video, Apple engineer’s have been building PointOfInterest app, giving it views for finding various point of interest around San Jose and listing how far they are away from you.

Considering PointOfInterest app, Lets discuss about how we normally write code and how we can restructured it to make testable.

  1. Some strategies for testing networking code in your app
  2. Some tips for tests dealing with foundation notification objects
  3. Ways to take advantage of protocols when making mock objects in your tests
  4. A few techniques for keeping your tests running really fast.

Strategies for testing networking code in your app

Let’s start talking about networking. To allow for dynamic content updates, we’ve been building our app to load its data from a remote web server.

Here, we see the high-level data flow involved in making a network request in the app and feeding the data into the UI.
In an early prototype of the app, we had a method in our class that was doing all of this in a single place, and it looked like this. The method takes a parameter with the user’s location and uses that to construct a URL for a service API endpoint with a location as query parameters.

Then it uses Foundation’s URLSession APIs to make a data task for a get request to that URL. Then the server responds, it would unwrap the data, decode it using foundation’s JSONDecoder API, into an array of point of interest values, which is a struct that I declared elsewhere and conforms the decodable protocol.

And it stores that into a property to drive a table view implementation, putting it onto the screen. Now, it’s pretty remarkable that I was able to do all of this in just about 15 lines of code, leveraging the power of Swift and Foundation, but, by putting this together in the one method, I the maintainability and especially the testability of this code.

Let’s first consider the request preparation and response parsing steps.

In order to make this code more testable, we started by pulling it out of the view controller and made two methods on this dedicated PointsOfInterestRequest type, giving us two nicely decoupled methods that each take some values as input and transform them into some output values without any side effects. This makes it very straightforward for us to write a focused unit test for the code.

Here, again, we pull it out the Class, made a APIRequest protocol with methods matching the signature of the methods from the request type that we just saw.

And this is used by an APIRequestLoader class. That’s initialized with a request type and a urlSession instance.

This class has a loadAPIRequest method which uses that apiRequest value to generate a URL request. Feed that into the urlSession, and then use the apiRequest again to parse in your response. Now, we can continue write unit test for this method, but right now I actually want to look at a midlevel integration test that covers several pieces of this data flow.

Another thing that I really want to also be able to test at this layer of my suite is that my interaction with the URLSession APIs is correct. It turns out that the foundation URL loading system provides a great hook for doing this. URLSession provides a high level API for apps to use to perform network requests. Objects like URLSession data tests that represent an inflight request. Behind the scenes though, there’s another lower-level API URLProtocol which performs the underlying work of opening network connection, writing the request, and reading back a response. URLProtocol is designed to be subclassed giving an extensibility point for the URL loading system.

Foundation provides built-in protocols subclasses for common protocols like HTTPS.

But we can override these in our tests by providing a mock protocol that lets us make assertions about requests that are coming out and provide mock responses.

URLProtocol communicates progress back to the system via the URLProtocolClient protocol.

We can use this in this way. We make a MockURLProtocol class in our test bundle, overriding canInit request to indicate to the system that we’re interested in any request that it offers us.Implement canonicalRequest for request, but the start loading and stop loading method’s where most of the action happens. To give our tests a way to hook into this URLProtocol, we’ll provide a closure property requestHandler for the test to set. When a URLSession task begins, the system will instantiate our URLProtocol subclass, giving it the URLRequest value and a URLProtocol client instance.

Then it’ll call our startLoading method, where we’ll take our requestHandler to the test subsets and call it with a URLRequest as a parameter. We’ll take what it returns and pass it back to the system, either as a URL response plus data, or as an error.

If you want to do test request cancellation, we could do something similar in a stopLoading method implementation.

Here In PointOfInterestRequestTests, we’re testing the makeRequest method just by making a sample and put location, passing it into the method, and making some assertions about its return value. Similarly, we can test the response parsing by passing in some mock JSON and making assertions about the parsed result.

One other thing to note about this test is that I’m taking advantage of XCTest support for test methods marked as throws, allowing me to use try in my test code without needing an explicit do catch block around it. Now, let’s see the code for interacting with URL session.

With the stub protocol in hand, we can write our test APILoaderTests. We set up by making an APIRequestLoader instance, configure it with a request type and a URLSession that’s been configured to use our URLProtocol. In the test body, we set a requestHandler on the MockURLProtocol, making assertions about the request that’s going out, then providing a stub response. Then we can call loadAPIRequest, waiting for the completion block to be called, making assertions about the parsed response. Couple of tests at this layer can give us a lot of confidence that our code is working together well and, also, that we’re integrating properly with the system.

References:

  1. https://developer.apple.com/videos/play/wwdc2018/417/?time=44