Swift Localhost: Making XCUITest Great Again
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
Here are 4 easy steps (L-A-I-M) to setup localhost XCUITest in your iOS project.
Install SwiftLocalhost (https://github.com/depoon/SwiftLocalhost) into your iOS project.
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.
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.
Note: Ideally we should take advantage of XCScheme to switch between domains for building production or test apps.
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.
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.
In the above example, all URLRequests with domain “www.googleapis.com” will be redirected to localhost.
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.
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.
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.
Using 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.
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?
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.
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
Speaker: Kenneth Poon , spgroup.com.sg 'If I don't have control over my environment dependencies, how can I test all…engineers.sg
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 email@example.com when you manage to succeed or require additional help to get it working.