XCUITest Stubbing Network Calls

WireMock Implementation

The Good

Within minutes, you can have your WireMock implementation ready to go. We used the standalone server implementation which we could then run the instance locally or on a shared resource. Its structure is pretty simple — it has two main folders, __files and mappings. The mappings folder contains the files which specifies the URL Requests to match on and the Response to send back. The __files folder contains the response you want to send back. A simple example:

{
"request": {
"method": "GET",
"url": "/people"
},
"response": {
"status": 200,
"headers":
{
"Content-Type" : "application/xml"
},
"bodyFileName": "/people.xml"
}
}

The Problem

Going to use a simple example to illustrate (your application workflow will end up being a lot more complex i’m sure):

{
"request": {
"method": "GET",
"url": "/list"
},
"response": {
"status": 200,
"headers":
{
"Content-Type" : "application/xml"
},
"jsonBody": []
}
}
{
"request": {
"method": "GET",
"url": "/list",
"bodyPatterns": [
{
"contains": "tom"
}
]
},
"response": {
"status": 200,
"headers":
{
"Content-Type" : "application/xml"
},
"jsonBody": []
}
}

Our Solution

Integrating SBTUITestTunnel allowed us to stub network calls with much more control.

import SBTUITestTunnelServer
import SBTUITestTunnelClient
struct StubHelper {
static func stubFile(fileName: String, url: String) {
let responseFile = SBTStubResponse(fileNamed: fileName)
app.stubRequests(matching: SBTRequestMatch(url: url),
response: responseFile)
}
}
import XCTest
import SBTUITestTunnelServer
import SBTUITestTunnelClient
class MyShoppingListTest: { var app: SBTUITunneledApplication override func setUp() {
app.launchTunnel()
}
override func tearDown() {
app.stubRequestsRemoveAll()
}
func testNoItems() {
StubHelper.stubFile(fileName: "listNone.json", url: ".*/list")
... login and perform any verification ...
}
func testOneItem() {
StubHelper.stubFile(fileName: "listOne.json", url: ".*/list")
... login and perform any verification ...
}
func testTwentyItems() {
StubHelper.stubFile(fileName: "listTwenty.json", url:
".*/list")
... login and perform any verification ...
}
}
import XCTest
import SBTUITestTunnelServer
import SBTUITestTunnelClient
class MyShoppingListTest: { private static var hasRunAtLeastOnce = false
var
app: SBTUITunneledApplication
override func setUp() {
if hasRunAtLeastOnce == false {
hasRunAtLeastOnce = true
app.launchTunnel()
StubHelper.stubFile(fileName: "listNone.json (see note
below)", url: ".*/list")
... login ...
}
}
override func tearDown() {
app.stubRequestsRemoveAll()
}
func testNoItems() {
StubHelper.stubFile(fileName: "listNone.json", url: ".*/list")
... refresh the list (i.e. swipe down to refresh) ...
...verify list...
}
func testOneItem() {
StubHelper.stubFile(fileName: "listOne.json", url: ".*/list")
... refresh the list (i.e. swipe down to refresh) ...
...verify list...
}
func testTwentyItems() {
StubHelper.stubFile(fileName: "listTwenty.json", url:
".*/list")
... refresh the list (i.e. swipe down to refresh) ...
...verify list...
}
}
struct People: Codable {
var name: String
var age: Int?
}
{
"name": "Tom",
"age": 10
}
class PeopleStub: Codable {
var data: People!
init(fileName: String = "OnePerson.json") {
let json = readFile(name: fileName)
self.data = try! JSONDecoder().decode(T.self, from: json)
}
func readFile(name: String) -> Data {
let nameAndExtension = name.components(separatedBy: ".")

guard let path = Bundle(for: type(of:
self)).url(forResource: nameAndExtension.first ?? "",
withExtension: nameAndExtension.last ?? "")
else {
fatalError("File: \(name) doesn't exists")
}
return try! Data(contentsOf: path)
}
func stub() {
let url = ".*/people"
let data = try! JSONEncoder().encode(self.data)

app.stubRequests(matching: SBTRequestMatch(url: requestURL,
query: queryParams), response:
SBTStubResponse(response: strJson))
}
func stubReallyLongName() {
self.data.name = "Thomas John Black Somereallylonglastname"
self.stub()
}
func stubNoAge() {
self.data.age = null
self.stub()
}
}
import XCTest
import SBTUITestTunnelServer
import SBTUITestTunnelClient
class PeopleTest: { var app: SBTUITunneledApplication
let peopleStub = PeopleStub()
override func setUp() {
app.launchTunnel()
}
override func tearDown() {
app.stubRequestsRemoveAll()
}
func testLongName() {
peopleStub.stubReallyLongName()
... perform steps that would trigger the people API call...
}
func testNoAge() {
peopleStub.stubNoAge()
... perform steps that would trigger the people API call...
}
}

--

--

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