There is no doubt that Swift is an awesome language for developing native apps for Apple platforms like iOS, macOS, watchOS, and tvOS. However, being a new language, Swift lacks a lot of testing features like we see in other programming languages like Ruby. Mocking classes with protocols in Swift is terribly hard and there are limited options to stub the network requests. 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. The stubbing is another way to test our code without having to rely on backend or network. 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. Some of the most popular libraries are OHHTTPStubs, Mockingjay, Hippolyte for XCTest(unit) testing. 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). However, there are new server-side Swift frameworks emerging in the market like Vapor, Kitura, Perfect which also can be used for the stubbing the network requests. In this post, we will see how to how we can use the Vapor server side swift framework to stub the network requests.
Stubing Network Requests for XCUITests
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.
- Stub 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.
In my previous post on Network Stubbing options for XCUITest, we have covered all the options like Swifter, SBTUITestTunel, and Embassy for XCUITest. Now let’s cover the stubbing with Vapor in details.
Stub using Vapor: Example App
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. The reason for choosing Vapor is that it’s fairly easy to learn and its community is growing fast around vapor. Its purely written in the Swift and comes up with the Vapor toolbox which is easy to get started.
In order to demonstrate this feature, I have created a demo app that displays Github user information when we enter Github username in the text field. You can clone that app from Github Vapor-XCTest which has XCUITests with and without the stub.
You need to have Xcode 9+ and OpenSSL installed then we can check out the demo app.
1 2 3 |
$ git clone https://github.com/Shashikant86/Vapor-XCTest $ cd Vapor-XCTest $ open Vapor-XCTest.xcodeproj |
This is example app showing the Github user information. It asks users to enter your Github username and once clicked on Submit button, it displays all the user details. Now that, you will see an example app with the couple of demo UITests. One with real network request and another with stubbed network request with Vapor
Try, running LocationCheckWithoutStub.swift which makes the real network request. You can see that it uses the launchEnvironment value from real server app.launchEnvironment = ["BASEURL" : "https://api.github.com"] to make real network request.
Now, navigate to the Vapor-Server directory where we have all Vapor setup. Build the project and run the server. The server will send the dummy response that stubbed in the main.swift file.
1 2 3 |
$ cd Vapor-Server $ swift build $ .build/debug/Vapor-Server serve |
This will start vapor server on port 8080 and listen to the response from local vapor server.
Now run the UITest LocationCheckWithVaporStub.swift which uses local response, you can see that launchEnvironment value fro local network. app.launchEnvironment = ["BASEURL" : "http://localhost:8080"] You can see that test will pass with the response Stubbed data.
Stub using Vapor: Your App
Now that, we have seen that example app stubbed with the stubbed data using Vapor. Let’s explore the process of how we can set up your app to use stubbed responses of Vapor server.
- Allow Local Networking
In order to use stubbed data from Vapor, we need to do the little bit of setup to configure our main app to listen to the local network calls. This can be done by the editing the main app Plist file by adding the key
1 2 3 4 5 6 7 8 9 |
<key>NSAppTransportSecurity</key> <dict> <!--To support localhost --> <key>NSAllowsLocalNetworking</key> <true/> <!--To continue to work for iOS 9 --> <key>NSAllowsArbitraryLoads</key> <true/> </dict> |
This will allow the networking for iOS app. However, you should do this only for the debug build configuration or similar not for the release configuration.
- Setup Launch Arguments/Environments to use local responses
In order to replace the real network requests with local requests. We need to setup some Launch Arguments/Environments that can be used by the XCUITests. By doing that, we can do the tests for both real requests and stubbed requests by passing launch arguments or environments.
- Get Vapor inside the iOS app
There are the couple of ways to get Vapor in the iOS project. We can either use Vapor toolbox or we can get Vapor using Swift Package Manager as mentioned here. I would recommend getting it using Swift Package Manager as we probably don’t need much of the web framework that Vapor toolbox provides. Refer an example of Vapor Server from the demo app and package.swift file here
- Start Stubbing network requests
Once you got all the necessary tooling, it’s time to actually stub network requests. Vapor has comprehensive documentation on how to work with requests and responses in JSON. Like in the demo app we have stubbed the request to /users/shsahsikant86 to return stubbed response.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import Vapor let drop = try Droplet() drop.get("users/shashikant86") { request in var json = JSON() try json.set("location", "StubLocation") try json.set("name", "StubName") try json.set("blog", "http://shashikantjagtap.net") try json.set("followers", 100000000000000000000000000000) try json.set("public_repos", 100000000000000000000000000) return json } try drop.run() |
You can see that with stubbing we can actually test the edge cases and scenarios that are hard to test with real data. The good example would be how do you test demo Github app for the user who has trillion followers. In real life, nobody has that much followers but we can stub it and test it as shown in the code above.
I have given a presentation on Vapor London, Meetup at BBC office. The slides are below
Conclusion
Using Server Side Swift Frameworks like Vapor and others, we can stub the network requests that can be used by XCUITests for running user interface tests. We can take benefits of Swift language both for stubbing network calls which eliminate need to run the servers using other programming languages like Ruby or Java. What do you think of this approach? Or do you still prefer nodeJS, Sinatra servers Or would you use lightweight servers like Swifter for stubbing requests for XCUITest? Contact me on Twitter @Shashikant86