Stubbing HTTP Response by Using Apple-Authorised Man-in-the-Middle Attack

How URLSession works and how to stub responses by subclassing URLProtocol

Eric Yang
Eric Yang
Oct 16 · 5 min read
aerial photo of car driving on a highway next to the ocean
aerial photo of car driving on a highway next to the ocean
Photo by Pat Kay on Unsplash

iOS apps interact a lot with the network. They read or write state to or from the server, and fetch data, images, audios, and videos from remote. To protect and verify the network layer, we write unit tests around it. Sadly, if we write tests that rely on networking, they will be slow and unstable. And to be fair, they are not really unit tests but more integration tests.

How can you stub the network request and response and isolate the unit test’s code from networking without installing any third-party libraries? Registering your own instance of URLProtocol at the URLSession configuration is the key:

The following explains the code in detail.

road sign warning of curves ahead on a mountain road
road sign warning of curves ahead on a mountain road
Photo by NOAA on Unsplash

1. The Core Flow of the URL Loading System

diagram of default flow of URLSession
diagram of default flow of URLSession
URLSession Default Flow by Eric Yang

URLSession

URLSession plays the core role of the iOS URL Loading System. The instance of URLSession creates one or more instances of URLSessionTask, which can be the instances of its subclasses:

  • URLSessionDataTask: Fetch and return data to your app
  • URLsessionUploadTask: Upload data and files to the remote
  • URLSessionDownloadTask: Download data and files from the remote
  • URLSessionStreamTask: Read and write from and to the remote by using an enqueued and executed serially TCP/IP connection
  • URLSessionWebSocketTask: Read and write asynchronously from and to the remote by using TCP and TLS in the form of WebSocket framing.

URLSessionConfiguration

We use URLSessionConfiguration to configure the instance of URLSession, which controls the behavior, like how to use caches and cookies or whether to allow connections on a cellular network.

One instance of URLSession contains only one instance of URLSessionConfiguration but can create multiple instances of URLSessionTask repeatedly. To achieve a different cache or cookie policy, we need to use different configurations at different sessions.

Completion handler

When the instance of URLSessionTask reaches its result, it delivers the server’s response, data, and possible errors to the Commpletion Handler. We verify the error parameter and check the response status code and data there.

Normally, when sending the HTTP request, we use the default instance with the default configuration to create a data task with the request:

2. Register Custom Request Handler to Stub

diagram of URLSession flow After Registering URLProtocols
diagram of URLSession flow After Registering URLProtocols
URLSession Flow After Registering URLProtocols by Eric Yang

Although Apple hides the underlying complexity of the URL Loading System under the URLSession, there are still Hooks to provide configuration when needed. When we register the subclass of URLProtocol to the configuration of the instance of URLSession, it gives us the capability to intercept the requests and the responses.

URLProtocol

URLProtocol is the most obscure and powerful part of the URL Loading System. It’s an abstract class and has four important and powerful methods to be implemented:

  1. canInit(with:)

URLSession checks the registered protocols to confirm if it can handle the request when the request is loaded. The registered protocols are consulted in reverse order of when they are registered.

The first protocol to respond true with the canInit(with:) function gets to handle the request.

2. canonicalRequest(for:)

This is where we can alternate the request if needed.

It is up to each concrete protocol implementation to define what canonical means.

3. startLoading()

This is the most important method to return the stub response. It has the instance of URLProtocolClient, which is used to communicate with the URL Loading System.

Don’t implement the URLProtocolClient protocol in your application. Instead, your URLProtocol subclass calls methods of this protocol on its own client property.

4. stopLoading()

When this method is called, the protocol implementation should end the work of loading a request. This could be in response to a cancel operation, so protocol implementations must be able to handle this call while a load is in progress.

This method is required at the subclass of URLProtocol but not required to do anything.

small red car being on a highway
small red car being on a highway
Photo by Ben Neale on Unsplash

3. The sample project

To demonstrate how to implement the flow with an easily extended and testable network layer, I build the sample project against RESTful API service.

diagram of Different Networking instances from App and Unit Tests
diagram of Different Networking instances from App and Unit Tests
Different Networking instances from App and Unit Tests by Eric Yang

The networking protocol

Since we are building the project around stubbing the network response, I’m going to start from the network level.

Every time we come up with a new object, it’s a good idea to start from the protocol. You’ll see that doing this allows you to write code that’s highly testable, flexible, and focused.

Let’s write a protocol definition for the networking, with the following three goals:

  • The networking can provide the customized instance of URLSession.
  • The networking accepts the endpoint’s request, executes it with the specified instance of URLSession, and returns the response in the desired global dispatch queue.
  • The networking can decode the data into an appropriate model.

By implementing the extension of Networking protocol, we have the default implementation.

The DefaultNetworking for app

Now we can have the DefaultNetworking for normal service usage at the app.

The MockNetworking for unit tests

  • We get the ephemeral configuration from URLSessionConfiguration.
  • And then we register the MockURLProtocol with the ephemeral configuration.
  • We generate the custom instance of URLSession for mock networking.

The MockURLProtocol adopts URLProtocol

  • We adopt the URLProtocol for the subclass MockURLProtocol.
  • There are two properties, one for setting stub error and another for stub response.
  • We implement the methods to handle the request and return the stub response.
football player catching a pass
football player catching a pass
Photo by John Torcasio on Unsplash

4. Unit tests With Stub Response

Woohoo, we can stub response for our unit tests now!

To do that, add the sample response as JSON, and pass the encoded data to the request handler. Make the service request by using the MockNetworking, and enjoy the stub response!

parking meter in front of empty street with buildings in the background
parking meter in front of empty street with buildings in the background
Photo by Hafidh Satyanto on Unsplash

Conclusion

The article describes how we stub response by registering the subclass of URLProtocol with the URLSession configuration. The URLProtocol plays the key role here. It works like the Apple-authorised man-in-the-middle attack.

There is a lot of potential we can explore with the URLProtocol without changing anything else about how requests are loaded.

Thank you for reading, and please leave any questions you might have in the comments.

All code mentioned above can be found in this GitHub repo.

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store