Network Stubbing options for XCTest and XCUITest in Swift

Published by Shashikant Jagtap on

Every iOS application requires the data to be displayed in the app. Unfortunately, we can’t put all the data inside our iOS app. The iOS developers have to make the network requests to get this data from internet and use it in the apps. Apple has provided various libraries and frameworks like URLSession, NSURLProtocol deal with networking layer and there are some third-party frameworks like Alamofire available to deal with network requests. However, testing of the asynchronous code in the form of network requests became very complex due to different approaches used whilst testing network layer. In this detailed post, we will explore all the available options to test network layer in Swift and cover details of network stubbing libraries available to use with XCTest framework.

Network Testing in Swift

The network testing of the iOS app written in Swift can be performed at Unit, Integration or UI level depending on the project needs. The unit and integration tests are generally fast and stable on other hand UI tests are slow and brittle. From what I have been reading on the internet, the people are doing network testing in iOS using various approaches, some of the common approaches are as follows

  • Mocking classes with protocols in the Unit or Integration Tests

Mocking is fragile and hard, it gets harder when it gets to Swift. There are no mature mocking libraries available in Swift to generate mocks like it exist in Java, Ruby, Python or other languages. The developer has to write all the mocks by hand and tightly couple the test code with production code. There is the great article on network testing in iOS here to understand more about how to mock with protocols. This approach definitely makes the app more testable but it involves writing lots and lots of protocols and mock classes. In Swift, we need to mock most of the classes and methods that we need to test. This is going to be the lot of work for the iOS developers and soon it became chaos.

  • Recording and playing back network requests

Swift has some libraries that allow us to record the network requests and play it back to avoid the unit tests going through the networking layer. They store the recorded data in the files and that data is reused instead of making network calls. The most popular libraries are DVR which make fake NSURLSession requests for iOS apps. There is one more library Szimpla which does the similar thing. You can watch the session on Szimpla here and the session on DVR here on Realm academy.

  • Stubbing the Network Requests using libraries

There is another approach to network layer which is making the network call instead of mocking URLSession and return stubbed or static response instead of real response. Using stubs we can still achieve the goals of network testing as well as we don’t have to tightly couple test code to production code. We will see details of the libraries that can be used for the stubbing the network requests with Swift. Some of the most popular libraries are OHHTTPStubs, Mockingjay, Hippolyte for XCTest(unit) testing.

  • Interacting with UI using XCUITest frameworks

The user interface tests go through the network layer and cover testing all the aspects of the network testing. Apple has XCUITest framework to cover Xcode UI testing. There are some libraries which we can use to stub network for UITest. Some of the popular libraries are Swifter, SBTUITestTunel and Embassy for XCUITest(UITest).

All the approaches mentioned above have their own pros and cons so its essential to pick one that suits the project need. In this post, we will see how to stub out network layer for unit and user interface tests.

Git Location App

We will be using GitHub API for this entire demo. We will make the network request to URL  https://api.github.com/users/shashikant86  and get the location from API and display in the app. You can see the JSON response by visiting this in the browser. Our App has a button “Make Network Request” once pressed it shows the Location from API or from stubbed data. Don’t worry if you not following at this point, we will get the source code of app with all tests at the end of this post.

Stubbing Unit Tests(XCTest)

Before we dive into stubbing lets see how we can test Github location by making real API call with XCTest Framework. Ideally, we can have API Client in the app and pass URL to the client and assert that we get right data but I am the lazy developer and didn’t make my code testable at all. In this case,  we have to make the direct network call from the test. The typical test will look like this:

In this test above, we are making network call using URLSession and parsing JSON response to check for name and location field. This test will pass but it has few drawback. The result of the test depends on the network, if the network went off, out test will fail. The test will be incredibly slow as it makes the real network request and waits for the response. These kinds of tests would be very brittle, fragile and unmaintainable. Ideally, we should be able to stub the response and make the test network dependent. Stubbing the network calls will allow us to test the edge case scenarios. The network requests can be easily stubbed at unit level using XCTest framework. This can be achieved by some of the open source libraries that we will be cover briefly.

Mockingjay

The Mockingjay is the library which allows us to stub the network call and return the response we expect. It can be installed using Cocoapods. Carthage installation isn’t mentioned in the README of this project so good idea to use Cocoapods. We can stub the URL to respond with JSON data that we want. e.g if we want the location to be Paris then we can stub the response using Mockingjay like this:

Now the Github API will respond with the location of Paris instead of London. We can assert that location in the test above as  XCTAssertTrue(result["location"] as! String == "Paris")  We can also create a JSON file e.g Feed.json in the test target and stub the response from the static JSON file.

In this way, we can stub the API response using Mockingjay. At the time of writing this post, Mockingjay works for Swift 3.2 projects and had some issues with Swift 4 projects.

Hippolyte

The Hippolyte is another stubbing library that is Switten in Swift to stub the request and response. We can install Hippolyte using either Cocoapods or Carthage. This is much lighter and easier way to stub the requests and responses. You then register this stub with Hippolyte and tell it to intercept network requests by calling the start()  method. In our example above, we can easily stub the data from the JSON file using following code

Now the response to Github API will use the stubbed calls from the Feed.json  file and we can assert the location mentioned in this file. Hippolyte is lightweight and can be happily used for stubbing requests and responses but it might not have advanced features that you are after check out the full features offered by Hippolyte on Github  here

OHHTTPStubs

The OHHTTPStubs framework is been around since the Objective-C days. This framework was being used to stub the Objective-C stubs for iOS apps. Although the roots of the OHHTTPStubs in the Objective-C, it has the wrapper that works for the Swift as well. We have to install OHHTTPStubs using Cocoapods and have to install both Objective-C and Swift version in the Podfile as follows:

In our example above we can stub the request and return the response from static file using OHHTTPStubs

Now the response to Git API will use the stubbed calls from the  Feed.json  file and we can assert the location mentioned in this file. OHHTTPStubs is quite a mature library with full feature set but it was originally designed for the Objective-C.

Stubbing UI Tests(XCUITest)

Apple has announced the UI Testing support in Xcode at WWDC 2015. The UI tests are designed to be the completely black box without having any access to main application code and data. The XCUITest uses two standalone processes Test Runner and Target app, the test runner launched the target app and uses the accessibility capabilities built into UIKit to interact with the app running in a separate process. While mocking or stubbing UITest we have few options

  • Mock and Pass Launch Arguments/Environments

This means you can not mock the API directly. The only way to communicate to the proxy app is to pass the Launch Arguments or Launch Environments. We can pass mock the API and create a launch environments variables and pass it to XCUITests but it requires the lot of hard work. The code is surrounded if-else statements to determine the build configuration which doesn’t sound great.

  • Mock Backend with the web server and return responses

The other options remain is to stub the network calls and get the desired data back from the server. In order to do this, we need to mock the backend and point our app to use those endpoints. The question is can we still use stub services mentioned above for unit tests. We can still use those libraries to stub out the network requests but sometimes it requires the change in the logic of the main app and needs to refactor things which add extra work. We need to add a lot of test code in the production code.

The good approach would be to run a web server locally or remotely to control the data that we needed. Also, it should require fewer changes in the application code. As far I read on the internet, some companies following good approach to stub UI Tests. Some of the approaches mentioned below come from their experience and some of them I tried myself. There are ways we can achieve the stubbing the network requests for UI tests. Let’s consider each of them briefly. You may notice that libraries available to stub UI Tests are completely different than those we used for unit tests as additional work requires to get the libraries working with UITests.

Swifter

Swifter is HTTP engine written in Swift which currently works in sync style but next version is planned to be in async style. The Swifter approach to stub UI Test is originally published on this article in details, so I should not duplicate that but in a summary, Swifter does following

  • Runs server inside the test process so that we don’t need to explicitly start or stop the server.
  • Completely isolated from app code and everything stays inside UI tests target.
  • CI Server integration doesn’t need additional setup
  • We can use initial stubs as well load dynamic stubs while test execution

The only thing we need to do is point our app to localhost by changing the Info.plist  file. We can add following code to enable local networking

Swifter is available to download using Cocoapods, Carthage and Swift Package Manager. Again all the instruction to setup XCUITests is in the blog post here.

Embassy

Another great approach followed by the Envoy to stub network request for XCUITests. They have written the great blog post about it. Basically, they created the couple of libraries to deal with network request stubbing for UI Tests. The libraries are Embassy and Ambassador. You need to read this article to get you started with Embassy which works for latest Swift version as well. In a summary Embassy approach is

  • Lightweight and Asynchronous event loop based Style
  • Bit of setup code we need to write/copy to get going with Test
  • Define router and provide your stubbed response

SBTUITestTunnel

SBTUITestTunnel is another library which can be used to stub network requests as well as to do some other stuff like interacting with NSUserDefaults, download/upload files from/to the app’s sandbox, monitor network calls, define custom blocks of codes executed in the application target. There is an article on how to use SBTUITestTunnel for network stubbing also the README file with setup and usage guide. In summary, SBTUITestTunnel needs following things.

  • SBTUITestTunnel need server and client libraries installed, Server needs to be installed on main target and client needs to be installed on the UITest target.
  • In the main target, we need to change build configuration and add pre-processor macro  as well as in the AppDelegate we need to take off server for the debug or test configuration
  • In the test itself, we can stub the network call to get the desired response.

Server Side Swift Frameworks: Vapor

The Server Side Swift frameworks are not production ready yet but we can use it for the purpose of stubbing the XCUITest. I haven’t read anybody doing that on the internet but it works perfectly. There are few Server Side Swift frameworks available in the market e.g Perfect, Kitura, Zewo and Vapor but for this demo, we will use Vapor. I will probably write the detailed article on this topic but for now, let’s talk about it briefly.

Get the Vapor framework using Swift Package Manager as mentioned here. We need to tell Vapor to return response we want when specific endpoints are called inside the main.swift  file

Now that, we told the server to return location as Vapor  when API endpoints are called. We can build the server and start it using

Now, you should see the server running on port 8080 serving the desired response. We can write a test as usual passing launch environment pointing to localhost.

Non-Swifty Web Servers

There are still some people who prefer to run web servers which are written in other crazy programming languages like Java, Ruby, NodeJS etc. There are various option to run non-Swifty web servers and most common are

Enough Talk! Show Me the Code

At this point, if you are still reading, you might be wondering, this guy talked a lot about various libraries but where is the fucking code. As promised earlier, there is code for everything talked about earlier and I have tried each and every framework mentioned above apart from non-Swifty web servers. The Source code is available on Github

Shashikant86/SwiftStub-XCTest

The SwiftyStub project has GitHub location app as mentioned above and covered both unit and UI tests using all the libraries mentioned above. The app has environmental variable BASEURL  passed to the scheme so that we can override it with localhost whilst UI testing.  Some of the libraries aren’t compatible with Swift 4 so this project is built using Swift 3.2.

You can try it by your own. Just clone the repo

In the unit test target SwiftStubTest there are stubbed unit tests using Mockingjay, Hippolyte and OHHTTPStubs. Also, there is unit test making the real network request. In the UI test target  SwiftStubUITest we have UItest for stubbed networks using Swifter, Embassy and SBTUITestTunnel and real network test. You can execute each tests using Xcode diamond icon or press ‘CMD+U’ to execute entire test suite.

There is UI test uses Vapor which might fail which is expected. In order to execute this test, we need to build the server and start the service

Now, we can execute the Vapor UI Test inside the Xcode. Please take a time to go through the test and let me know if any improvements needed.

Which One to Pick?

The selection of the right library depends on the Skills of engineers in the team and project itself. The important factor to check is the Swift version compatibility

Unit Testing

If the team has used OHHTTPStubs from old Objective-C days then its worth carry on using for Swift project as well. If you just need the response stubbing then something lightweight like Hippolyte will do the job. Mockingjay is also good library but at the time of writing this post, it wasn’t working as expected with Swift 4 so worth checking before using the library.

UI Testing

I would not recommend the approach of running non-Swifty servers written in Java or another crazy language as it would be chaos if it stops working for an unknown reason. The Swifter wasn’t compatible to Swift 4 at the time of writing this post whilst pull request was open on Github. The swifter approach looks painless as the server starts and stops as part of the test process however it works in sync style. Other approaches like Embassy and SBTUITestTunnel worth giving a try also monitor the GitHub repo and see if they are still being maintained. At last, if you are super duper brave then go to Server Side Swift Frameworks. They are bit heavy for this job but they will work for sure!

Conclusion

The testing of the network layer is hard in Swift as it requires a lot of protocol mocking and dependency injection code all over the application. Using the lightweight third-party libraries to stub the network requests make it much simpler by making UI and unit tests independent of network and full control over data. What are your experiences of using XCTest with stubbed data? Have I missed good libraies? Which approach you used or liked from this post? Please mention in the comment.