Back in 2019 I wrote about Testing networking code with custom URLProtocol onĀ iOS. Recently, I was using the same approach for setting up network mocking for GraphQL requests, but then I stumbled on something unexpected. If we create an URLRequest which has httpBody set, then inside the custom URLProtocol implementation, httpBody method returns nil, and we need to access httpBodyStream instead. When dealing with GraphQL requests, the GraphQL query is part of the data set to httpBody. Reading the data from httpBody property would be straight-forward, then httpBodyStream on the other hand returns an InputStream and this requires a bit of code. InputStreams can only be read once, therefore while inspecting the URLRequest inside the URL protocol, we need to make sure not to read it twice. This is a nature of how input streams work.
In the snippet below, we can see a Data extension which adds a new initializer which takes in an InputStream. The implementation opens the stream, closes it when initializer is exited, reads data 512 bytes at the time. The read function of the InputStream returns number of read bytes if successful, negative value on error and 0 when buffer end has been reached which means that there is noting more to read.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Let’s see how to use this extension and reading a GraphQL query from the URLRequest. In the example below we can see that the example GraphQL data is set to a dictionary with 3 keys: query, operationName, and variables. Therefore, we need to first turn the InputStream into Data and then decoding the data to a model type and after that reading the query. Since we know the query, we can proceed with writing network tests where the mocked response depends on the query.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It is important to have stable unit-tests and UI-tests since no-one wants to encounter failures in tests which happen non-deterministically. Many of the iOS apps rely on networking, and therefore the content depends on what is fetched from servers. The last thing what we want to see is that an API outage or instability affects UI-tests. This time we’ll take a look at how to mock networking in UI-tests which differs from mocking networking in unit-tests since the whole app is going to be running as is and is controlled by a separate UI-testing runner app. A while ago I also covered unit-testing part in Testing networking code with custom URLProtocol onĀ iOS, please take a look at that post for more information since we’ll be using the same custom URL protocol approach.
The main difference between unit-tests and UI-tests is that with unit-tests Xcode injects the unit-testing code into the app and then runs tests. UI-testing on the other hand rely on a separate test runner app which uses accessibility APIs for driving the user-interface in the app. This means that our network mocking code needs to be bundled with the app when we build it. The approach we are going to use is setting up a separate “UITestingSupport” Swift package, which is included only in debug builds. This library contains mocked data and configures the custom URL protocol and handles any network requests. Please see Linking a Swift package only in debugĀ builds for more information on how to only link a package in debug builds.
I’ll be using a sample app “UITestingNetworking” for demonstrating how to set it up. The app has an app target and a local Swift package with a name “UITestingSupport”.
Xcode project layout with UITestingSupport package.
The first piece of the “UITestingSupport” package is a custom URLProtocol. All it does is providing a way to return either error or URLResponse and Data for a URLRequest. It is a simplified protocol. In an actual app we would want to control which requests are handled by it and which are not. Either because it is way too difficult to mock all the network request in all the tests at first, or we might also want to have some tests using an actual data coming from servers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The second piece of the library is a UITestingNetworkHandler class which the app code will call, and it configures the custom URLProtocol and starts providing responses based on the “responseProvider” callback.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Simple example of providing response for a URLRequest.
The example above just handles one network request. For larger apps we probably want to have more component based implementation here since this file would otherwise grow a lot based on how many cases we want to handle. Another thing to note is that in some tests we want to mimic network request failures and in others successful requests but with different response data. This is not shown above, but can be implemented by providing the expected configuration flag through environment variables. XCUIApplication has a launchEnvironent property what we can set and then reading that value in the UITestingNetworkHandler with Process environment property. I’m thinking something like “MyAppAPIExampleResponseType” which equals to a number or some identifier.
The last piece is to call the register code when we are running the app in UI-tests.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Calling register only in debug builds and when a launch argument is set by the runner app.
And finally, an example UI-test which taps a button which in turn fetches some data from the network and then just displays the raw data in the app.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Testing networking code might sound tricky at first but in reality, it just means using custom URLProtocol what returns data we would like to. This allows testing the networking module without mocking URLSession. Using this approach we could do so much more, even integrating a third party networking library.
Networking class wrapping URLSession
Firstly, let’s set up a simple WebClient class what uses URLSession for initiating networking requests. It has a fetch method for loading URLRequest and transforming the response to expected payload type using Codable. As payload can be any type, we use generics here. Note that we need to pass in the payload type as a variable because we need the exact type when decoding the JSON data. How can we test this as URLSession would try to send an actual request to designated URL? As unit tests should behave exactly the same all the time and should not depend on external factors, then using a separate test server is not preferred. Instead, we can intercept the request and provide the response with custom URLProtocol.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
URLProtocol is meant to be overridden. Firstly, we’ll need to override canInit(with:) and return true here allowing URLSession to use this protocol for any URL request. Secondly, it is required to override canonicalRequest(for:) where we can just return the same request. Thirdly, startLoading, where we have the loading logic which uses class property for returning appropriate response. This allows us to set this property in unit tests and then returning the result when URLSession handles the fetch request. Finally, URLProtocol also needs to define stopLoading method what we can just leave empty as this protocol is not asynchronous.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Using TestURLProtocol for mocking network requests in unit tests
Setting up a unit test requires to set the TestURLProtocol’s loadingHandler and returning the data we would like to. Then we create URLSessionConfiguration and set our TestURLProtocol to protocolClasses. After that we can use this configuration for initialising URLSession and using this session in our WebClient which handles fetch requests. That is pretty much all we need to do for testing networking requests.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Testing networking code at first might sound daunting. But actually it just boils down to using custom URLProtocol and providing response we need to in our test.
If this was helpful, please let me know on Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.