Mocking network calls while UI Testing

Image for post
Image for post
Photo by Goran Ivos on Unsplash

Looking into doing UI tests for an app, I was researching the best way to mock all network calls during the execution of the tests.

The motivation for mocking network calls is to eliminate uncertainty while executing our tests. We can not rely on the server to be running, the data our test case is based on to be always the same, etc. Additionally, mocking all the calls will make the tests run much faster.

The problem with UI Testing is the way Apple has set it up you got your app running in a process and in another the tests. UI Tests uses the accessibility capabilities built into UIKit to interact with the app running in a separate process. The problem that comes with this is that we can not inject mocks or stub networks calls the way we usually do in our unit tests.

From what I have been reading, there are two options to work around this limitation.

On one hand, we can put in our app’s main target all the fixtures (the files we’ll use as response to our mock calls, they can be jsons, xmls … whatever) we need, a mocking framework and when running UI Tests pass an environment variable as a way to tell our app to setup out mocks.

There are a couple of things that bother me with this approach. First, we are putting special code only for running tests, a practice that should be avoided as much as possible. This never looks pretty, can lead to bugs, makes code less readable and less maintainable.

The other option is running a server locally, embedded in the process where our tests are being executed, where we have stubed the network calls our tests are hitting and returning our fixtures there.

There’s a post by Michal Ciurus where he details the way he does it. In his case, stubbing in the app process was not an option for him because he needed to dynamically change the response from the server. He used swifter as a server, a minimalistic http server written in Swift 🔝. There are other posts using other servers, like this one from Fang-Pen Lin.

Michal shares the class we need to setup the server and stub our calls:

Helper class to setup up swifter and stub our calls

Then we simply use it like this:

We can either setup up constant stubs in the helper class or, if we need, dynamically set up new stubs inside a test like it’s shown above.

The only thing that needs to be done now is telling our app to use http://localhost:8080 as the endpoint of our calls. I had two xconfig files, Debug and Release, where I set the test and production endpoints. I created an additional one, UITesting, using http://localhost:8080 as the endpoint. Set this specific xconfig for UITesting as Build Configuration for the Test Action of the UI Testing scheme.

Now there was just a final obstacle in our road, App Transport Security (ATS). Introduced in iOS 9, Apple incentivized developers to use secured network connections over HTTPS in an effort to product user security and privacy. But swifter only works via HTTP. There is a way to add an exception in our Info.plist but it was advised by Apple to be temporary, prompting developers to secure the resources they use in their apps. Since 2017 Apple rejects apps that are using this exception, other than the cases where the developer can justify it.

There is a way to add an exception for localhost that does not require justification during app review, using the key NSAllowsLocalNetworking but that only works with iOS 10. NSAllowsArbitraryLoads can be used in iOS 9

NSAllowsLocalNetworking should be added to the plist with iOS 10. Source.

In my case, because I’m running the tests using iOS 10 only NSAllowsLocalNetworking is needed. This way our tests will run fine calling http://localhost:8080 and the app will use the responses we stubbed.

The advantage of this approach is that is very easy to setup and maintain, the server is started right from our code, and we don’t have to put any testing code in our app.

Written by

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