Easiest network stubbing for XCUITest

End-to-end (E2E) UI tests are not only fragile due to multiple dependencies, they are also slow and expensive to execute. In my previous employments, I remember how frustrating it was for our organisations to spend multiple hours running E2E tests but to have them failing due to local client side bugs. Such unnecessary failures can be avoided by having client side integration tests.

In addition to unit tests, iOS engineers are encouraged to write UI automation tests as part of the work needed to complete a feature. These tests will be using localhost to mock external dependencies. I will use this blog post to show how easy it is for anyone to setup localhost XCUITest for existing iOS projects using cocoapods and XCode.

Setup iOS XCUITest Localhost in 4 steps

Setup iOS XCUITest Localhost in 4 steps

Here are 4 easy steps (L-A-I-M) to setup localhost XCUITest in your iOS project.

(L) Localhost

Install SwiftLocalhost (https://github.com/depoon/SwiftLocalhost) into your iOS project.

‘SwiftLocalhost’ Entry in Podfile

Under the hood, this library uses (https://github.com/thecatalinstan/Criollo) library to create and manage the in-memory local webserver. SwiftLocalhost is a wrapper library that simplifies the managing of localhost server. This gives us the flexibility to improve our features by replacing the dependent library with another in future.

To begin, setup your XCUITest classes with the SwiftLocalhost library. Each test case will assign a unique random port number to the localhost server property. This will allow us to execute our UI tests in parallel.

Example of setting up localhost in XCTestCase

(A) API

In this step, we are going to redirect our existing outgoing API calls to hit our localhost server. Here is a simple example of domain string change.

Simple change of domain string

Note: Ideally we should take advantage of XCScheme to switch between domains for building production or test apps.

Port Numbers

As mentioned above, having unique port numbers assigned for each test case allows us to execute our tests in parallel. In our test cases, we need to pass the port number values into the launchArguments of XCUIApplication.

Passing localhost port number to the application under test before app launch

Redirecting domain used by 3rd party libraries

If your app is dependent other external domains used by 3rd party libraries in the app, you can use the NetworkInterceptor cocoapods library (https://github.com/depoon/NetworkInterceptor) to redirect all specified domains to the new one.

How to use NetworkInterceptor library to redirect external domains used by 3rd party libraries in your iOS app

In the above example, all URLRequests with domain “www.googleapis.com” will be redirected to localhost.

(I) Info.plist

In order for our app to send outgoing http requests, we need to patch the Info.plist file to indicate to include an exemption for localhost requests.

Preparing Info.plist for localhost testing

For iOS 9, we are required to add ‘localhost’ as an item under ‘Exception Domains’.

For iOS 10 and above, simply add and set NSAllowsLocalNetworking to YES.

(M) Mock

Create your helper class/function used to prepare the mocked URLResponse data. Here is where we can specify the location of our mock response files for our tests.

In this step, we will specify the mock responses to be returned for specific URLRequest paths.

Here’s the route function definition of LocalhostServer of https://github.com/depoon/SwiftLocalhost. You can also specify the response status code and headers needed to fulfil the use case requirements.

LocalhostServer ‘route’ function definition

Using SwiftLocalhost to assert outbound URLRequests

SwiftLocalhost to assert outbound URLRequests

All outbound URLRequest(s) hitting the localhost server are recorded. For a given use case, we can use SwiftLocalhost to assert that the iOS app is sending specific outbound requests in a particular order.

Partial definition of LocalhostServer

By inspecting “recordedRequests” property, we can inspect and assert outbound requests that hit the localhost server. The above example shows assertion of requests by URL paths. We can also assert requests by their queries, post body or even in cURL command format.

Question: Why use SwiftLocalhost over external mock servers?

Reasons for using SwiftLocalhost over external mock servers

Focus on Mocking

Engineers can simply focus on the contents of mock responses without knowing how to start a local web server. Remember, the goal of SwiftLocalhost is simply to achieve client side UI integration tests and not assert end-to-end behaviour.

Mocks in Test Cases

Setting up of mock responses are described in the same XCUITest test function. This helps to make the test cases more readable, especially when test codes and mock responses can be seen in the same scope.

Just Use Swift

You only require XCode and Cocoapods to get SwiftLocalhost running. iOS developers can stay in the comfort of using Swift as both the app development language and UI testing programming language.

Cmd + U

The strongest upside of using SwiftLocalhost is in its simplicity of execution. iOS developers can simply hit Cmd + U to trigger the UI automation tests. Hence there’s no need to use bash/shell to start up a local web server. In addition, continuous integration jobs only require to select the XCUITest scheme in order to run the tests. Yes, its that simple.

Showcase

To showcase how easy it is to setup SwiftLocalhost in an existing iOS project, I forked a iOS app repository project I randomly found on Github and applied the techniques highlighted in this medium post.

In addition, I also introduced a login feature into the app using Firebase Authentication to demonstrate how you can redirect 3rd party SDK’s URLRequests to localhost as well. You may watch the video below to observe request redirection in action (15th min onwards).

Here’s the video of the SwiftLocalhost talk I gave at iOSConfSG 2019

Make XCUITest great again, using localhost

Ending

I hope this article has given you insights on SwiftLocalhost. I strongly encourage iOS developers to use this technique as part of their everyday work. Do drop me an email de_poon@hotmail.com when you manage to succeed or require additional help to get it working.

[Email] de_poon@hotmail.com

[Twitter] @de_poonkenneth

[Linkedin] https://www.linkedin.com/in/kenneth-poon-84217019/

[Github] https://github.com/depoon