iOS & Swift Tutorials

Learn iOS development in Swift. Over 2,000 high quality tutorials!

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.

4.7/5 9 Ratings

Version

  • Swift 5, iOS 13, Xcode 11

WireMock is a development tool that provides a local copy of the results of a remote API call. You can use it in conjunction with User Interface (UI) tests to render different types of API responses.

UI testing acts as a safety net, ensuring you’re delivering the intended user experience. It focuses on the end-to-end experience of your app. As well, UI testing can answer different questions than unit testing can, such as:

  • What happens on this screen if my view is empty?
  • What happens if I get an error from a network request?
  • Does my validation UI show the correct information for different kinds of input?
  • Do my table cells reflect the data received?

Getting Started

First, locate the Download Materials button at the top or bottom of this tutorial. Download the starter project and open it. Build and run the project to check out the functionality.

The Star Wars Info app uses the Star Wars API to retrieve a list of Star Wars characters. You can tap each cell to navigate to a detail screen for that character.

Sample App

Running the Tests

The app is already set up with a UI testing target and two UI tests. The UI test target is already connected to the default Test action of the build scheme.

Run the tests by pressing Command-U or by clicking the menu item Product ▸ Test. You’ll see the app launch and navigate from the list view to the detail view and back. During this navigation, the UI tests are validating expected conditions on both the list and detail views.

To see the test report, go to the Report navigator in the left pane and view the test results:

As long as your computer is online and the tests can successfully hit the Star Wars API, the tests will pass.

UI Test Code

Look in StarWarsInfoUITests.swift to see the UI tests. The details of UI testing are beyond the scope of this article. Please see iOS Unit Testing and UI Testing Tutorial for a deep dive on that topic!

The sample project demonstrates best practices, including:

  • Use accessibility identifiers, not accessibility labels, to identify elements whenever possible. Benefits to this approach include:
    • Accessibility identifiers are not available to the user in any way, so you can name them whatever you want.
    • Identifiers remain constant. For example, a label may have different values but its identifier remains constant. So your tests will still be able to find the element on the screen.
  • A convenience method named waitForElementToAppear(_:file:line:elementName:timeout:) in an extension on XCTestCase allows for custom timeouts.
  • Passing the file and line numbers to convenience functions allows the UI testing system to report test failures at the call site.

Challenges With the Current Setup

This small app and the associated tests perform well in the current configuration, but as the code base grows, the app will rely on many more network requests. Tests will slow down and become flaky.

Here are some issues you can anticipate as the code base grows:

  • What happens if the network request you’re relying on fails?
  • What happens if the network slows down, causing timeouts or extended wait times?
  • What if the list request begins sending data back in a different sort order?

In all three scenarios, your tests would fail. There’s one common denominator. None of these test failures would be due to a programming error or logic issue. Test failures in these scenarios would be false positives, because the failure would be out of your control.

Mitigating Network Failures

In this tutorial, you’ll eliminate one of the sources of uncertainty in UI testing. You’re going to stop making live network requests and rely on a network mocking technology called WireMock. WireMock allows you to eliminate the network as a potential source of failure in UI tests.

Using WireMock, you’ll always get the same response for a given request on every test run. This allows you to have more confidence in your tests. If there’s a failure, you’ll spend less time trying to determine why. The network is one of the more volatile components in any application. By taking it out of the list of potential reasons for failure, you’ll be able to focus on the base functionality you’re trying to test with each screen.

Overview of WireMock

WireMock is an open source library that allows you to run a local web server for testing. By updating the API endpoints you’re calling from your app while running a test, you can return known responses. This can help you to move faster during development. During a test run, pointing your app at a WireMock instance allows you to receive expected responses from API requests.

Enough of the theory. Time to get started!

Setting Up WireMock

WireMock requires at least Java 7, so make sure your Mac has an updated version of Java installed. Download the latest Java Development Kit, JDK from Oracle. You’ll be running WireMock as a standalone instance. Go to the download page and get the latest stable build of WireMock. The download will be a single JAR file.

Go to the base directory of the starter project, and create a directory named Vendor. Move the JAR file into this directory. Rename the JAR file WireMock.jar to make it easier to interact with from the command line. Next, open Terminal and navigate to the Vendor directory you created.

Inside this directory, type the following command to start the WireMock process:

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

This command starts WireMock on port 9999 and turns on verbose mode. Turning on verbose mode allows you to see all messages, including URL matches and near misses as your app interacts with the WireMock endpoints. This is a big help when trying to determine why a WireMock endpoint may not be working.

Now, test that WireMock is running on your system. Open a browser and go to http://localhost:9999/__admin/mappings.

You should see the following:

{
  "mappings" : [ ],
  "meta" : {
    "total" : 0
  }
}

Great! This means WireMock is up and running. You’ll also notice WireMock has created two directories inside Vendor: __files and mappings.

Files and Mappings Directories

By default, the standalone instance of WireMock looks for these two directories relative to the executable’s location.

  • The __files directory contains the mock responses you want to send back for any given API requests.
  • The mappings directory contains files that map API request patterns to specific files that contain the desired response.

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.co/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.co/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.co/api/people"
  },
  "response": {
      "status": 200,
      "bodyFileName": "character-list.json"
  }
}

This lets Wiremock know to map any GET request with the /swapi.co/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.co/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.co/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.

Editing the Xcode Scheme

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.

Adding a Utility Function

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.

Checking for Local flag in Network Client

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.co/api/"
  } else {
    return "https://swapi.co/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

Perform a Run with WireMock

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.

Testing Against WireMock

Now, perform the final steps to get your UI tests set up with the right launch arguments.

UI Test Launch Arguments

Back in Xcode, expand the StarWarsInfoUITests group in the Project navigator. Then Control-click on the Utility group. Create a new file named LaunchArguments.swift. Make sure to check **StarWarsInfoUITests** and remove the check from **StarWarsInfo** in the **Targets** section of the **Save As** dialog.

Target membership

Add the following code:

struct LaunchArguments {
  static var launchLocalArguments: [String] {
    return [
      "-runlocal"
    ]
  }
}

Finally, expand the Tests group in the project navigator and open StarWarsInfoUITests.swift. In each of the two tests, right after the creation of the local app parameter, add the following line:

app.launchArguments = LaunchArguments.launchLocalArguments

This adds your -runlocal launch argument to each test.

Now you can test the UI test running against WireMock instead of the live network.

Switch back to Xcode and run your UI tests by pressing Command-U. If you watch Terminal while the tests run, you’ll see output from WireMock as your tests request data and they’re returned from the local server.

Tests Running

Congratulations! You’ve eliminated the network as a dependency for your UI tests.

Where to Go From Here?

In addition to mocking the same responses you always get from live network requests, you can now send back different scenarios to your UI and see how the app responds.

For example:

  • Does your app show proper error messaging if it receives an HTTP 500 status?
  • What happens to your UI if it receives an empty data set?
  • Does the app respond in the right way if it receives a malformed data set?
  • What happens if some of the strings received are too long for the UI? Does everything wrap or truncate as expected?

Try adjusting your mocked responses to test different flows inside your apps. WireMock also allows you to vary responses by many different variables for the same endpoint, such as query parameters and headers. Check out the documentation to see what else is possible.

Good luck, and please leave any questions in the comment section!

Average Rating

4.7/5

Add a rating for this content

9 ratings

Contributors

Comments