Local API Call Tutorial with WireMock and UI Tests in Xcode

Learn how to use WireMock, a tool you can use in conjunction with User Interface tests to provide a local copy of remote API call results. By Mark Struzinski.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Setting Up a Mock Response

The sample app uses one endpoint from the Star Wars API to request a list of characters. That endpoint is https://swapi.dev/api/people/.

If you go to this endpoint in a browser, the Star Wars API page does a nice job of showing you the JSON response, including headers. You are only interested in the response here.

People endpoint

Copy the full response excluding the headers:

{
  "count": 87,
  "next": "https://swapi.dev/api/people/?page=2",
  "previous": null,
  "results": [
    {
      "name": "Luke Skywalker",
      "height": "172",
      "mass": "77",
      "hair_color": "blond",
      "skin_color": "fair",
      "eye_color": "blue",
      "birth_year": "19BBY",
      "gender": "male",
      // Clipped for brevity here
  ]
}

Save this response into the __files directory as character-list.json.

Setting Up Mappings

Next, you need to let WireMock know how to map the people endpoint to this response. Create a new file in the mappings directory named character-list.json.

Make the contents of the file look like this:

{
  "request": {
      "method": "GET",
      "url": "/swapi.dev/api/people"
  },
  "response": {
      "status": 200,
      "bodyFileName": "character-list.json"
  }
}

This lets Wiremock know to map any GET request with the /swapi.dev/api/people path to the character-list.json response with an HTTP 200 success status.

Your final Vendor directory layout should look like this:

Vendor Directory Layout

Verify Functionality

Now, verify that WireMock has your mapping indexed. If your terminal is still running from your previous session, stop it by pressing Control-C, and start WireMock again using the command:

java -jar wiremock.jar --port 9999 --verbose

WireMock should have picked up your new mapping file. To check this, go to http://localhost:9999/__admin/mappings.

You should see the following output:

{
  "mappings" : [ {
    "id" : "5804b634-2a15-4fb0-a16e-2c19559f37df",
    "request" : {
      "url" : "/swapi.dev/api/people",
      "method" : "GET"
    },
    "response" : {
      "status" : 200,
      "bodyFileName" : "character-list.json"
    },
    "uuid" : "5804b634-2a15-4fb0-a16e-2c19559f37df"
  } ],
  "meta" : {
    "total" : 1
  }
}

This output means WireMock is picking up your mapping files. Next, check the response returned for your endpoint. Go to a browser and enter http://localhost:9999/swapi.dev/api/people.

You should see the following output:

LocalHost Output

Excellent! Everything is working from the WireMock side. Now you’ll integrate WireMock into your UI tests.

Integrating WireMock

To integrate the working instance of WireMock into your UI tests, follow these steps:

  1. Set up your project to allow HTTP loads from the locally running WireMock instance.
  2. Start your UI tests with launch arguments to pass a defined argument.
  3. Create a way to switch URL schemes in code based on your launch argument.

Allowing Loads from a Local HTTP Server

First, configure your project to allow HTTP requests from a non-SSL connection. If you don’t go through this step, App Transport Security (ATS) won’t allow network requests to connect to WireMock.

Allowing insecure loads is normally not advisable, but for UI testing, it’s easier than configuring a self-signed certificate to enable SSL from WireMock. In a more complex setup, you would configure the project to only allow insecure loads from your testing scheme with a build configuration setting or a different Info.plist for your test target.

Follow these steps to set up ATS for UI testing with WireMock:

  1. Open Info.plist in StarWarsInfo group.
  2. At the bottom, click + on the last row.
  3. Select App Transport Security Settings from the drop down list in the Key column.
  4. Press Tab.
  5. Select the disclosure triangle on the right so it is pointing down.
  6. Now, click + in the App Transport Security Settings row.
  7. From the drop-down list in the new row, select Allow Arbitrary Loads.
  8. Finally, in the Value column for this row, change the value to YES.

App Transport Security Settings

Defining a Launch Argument

Next, you’ll define a launch argument to let the app know when to switch URL schemes. When launching from Xcode, you can pass arguments to your app similar to the way you can pass arguments to a terminal command. Use Scheme Editor in Xcode for this.

You’re going to define the launch argument in the run action of the scheme. By configuring things this way, you can run the app in the simulator without any tests running. Once you’re sure everything is running the way you want, you’ll define those same arguments in your test code and launch the app with them. When running your UI tests, the app should behave the same way.

Click the StarWarsInfo scheme in Xcode and click Edit Scheme.

Edit Scheme

Select the Run action in the left-hand pane. In the middle pane, select the Arguments tab. In the Arguments Passed on Launch section, click + and add the launch argument -runlocal. Make sure to select the checkbox to the left of your new launch argument.

Adding a Launch Argument

Click Close to accept the change and return to your code.

Updating to Use Launch Arguments

Now you need to update the code base to look for this launch argument and use the appropriate URL scheme.

First, create a utility method to look for the launch argument. Control-click on the StarWarsInfo group, and create a new group named Utils. Then, Control-click on the new Utils group and create a new Swift file named StartupUtils.swift. Add the following code to this file:

struct StartupUtils {
  static func shouldRunLocal() -> Bool {
    return CommandLine.arguments.contains("-runlocal")
  }
}

This code inspects CommandLine.arguments, which is an array of strings. If that array contains the -runlocal argument, the function will return true.

Next, make use of your new convenience function in the network client. Inside the Network group, open StarWarsAPINetworkClient.swift. Update the property for baseURLString to a computed property that looks like this:

var baseURLString: String {
  if StartupUtils.shouldRunLocal() {
    return "http://localhost:9999/swapi.dev/api/"
  } else {
    return "https://swapi.dev/api/"
  }
}

If the -runlocal property is present on launch, the baseURLString will use the local scheme, connecting to the running WireMock instance for its data. Now you have full control over the data response from the API.

Open Terminal if it’s not already open and ensure WireMock is running. Navigate into the directory containing the WireMock executable. Run the same command to start WireMock:

java -jar wiremock.jar --port 9999 --verbose

With your launch arguments set, build and run by pressing Command-R. Watch the terminal while the initial list loads. You should see the console printing output as the app requests network data and WireMock returns it.