This article is the eighth in a multi-part series on test speed and reliability, inspired by a webinar I gave on the same subject (you can watch the webinar here). You might also want to check out the previous episodes on Test Flakiness, Finding Elements Reliably, Waiting for App States, Dealing With Unfindable Elements, Setting Up App State, Advanced Capabilities, and Disabling Animations.
Appium tests are usually written as end-to-end tests. In other words, they exercise the app from top (the UI) to bottom (backend services, which most apps employ). There is certainly value in this kind of testing. After all, your users are doing the same thing! Sometimes, though, what we care about with an Appium test is not whether the backend service works, but whether the App Under Test itself works, on the assumption that any data retrieved from an external service is valid.
The sad truth is that the Internet is not always very reliable. Requests made by an app to an external service might fail for a number of reasons, many of which would have nothing to do with the app itself. It could be a "stormy day" when it comes to "Internet weather", and requests could time out. Or the version of the backend service used for testing might not be able to handle the load of so many requests if you're running a lot of tests at once. The list goes on.
Luckily, it's possible to ensure quality for both the backend service and the mobile app in a much smarter way. The very fact that the Internet lies between these two components is the key: most backend API calls are just HTTP requests. This means that the backend service can be tested "end-to-end" by generating HTTP requests from something other than the app. This is called API Testing, and it's a much faster way to test your backend service than using Appium! Similarly, all the app UI tests care about is the data that comes over the wire from the backend service: it doesn't actually matter whether it's "real" data or not, as long as it's an HTTP response of the appropriate form, with content appropriate to the test. So, rather than having our app make calls to a real running API service, we could have it make calls to a "mock server" instead!
What are the benefits of using a mock server?
Using a mock server does have some downsides, however:
While these downsides are real, the speed and reliability gains are definitely worth it for any sizable build. So much for an introduction! Let's see how we would go about implementing this kind of strategy. Here's what we need to get it all working:
Putting all of these pieces together, the actual test flow would look like this (in pseudocode):
Before:
- Start mock server at certain port
- Start Appium session with app (connected to server)
Test:
- Set mock server expectations
- Use Appium to drive app, which makes calls to mock server under hood
- Verify app behaves properly given mock server responses
After:
- Quit Appium session
- Shut down mock server
The Mock Server library I linked above has the ability to do all of these tasks with ease. The server can be run from the command line, as part of a maven plugin, via a Java API, etc... We can set expectations in our Java code (or other language code) using a client that comes with the library. What does setting expectations look like? Here's an example that shows how we might mock a login request:
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/login")
.withBody("{\"username\": \"foo\", \"password\": \"bar\"}")
)
.respond(
response()
.withBody("{"\status\": \"success\"}")
);
Running this command at the beginning of a test (or test suite) directs the mock server to respond with a success JSON response to any login attempt with username foo
and password bar
. With this kind of setup, all we need to do is direct our Appium driver to enter these values into the login prompt of our app, and the mock server will receive the request and respond with the appropriate response we've entered here. As we mentioned earlier, it's important that both the request and response match the form of the real API service, otherwise we'll either run into failures or test the wrong conditions.
One difficulty in running mock API servers is encountered when utilizing a cloud service. One of the main benefits of mock servers is speed, and putting the Internet in between the app (running in the cloud) and your mock server (running locally) works against this benefit. Still, because the mock server doesn't actually have to do any work, it will be faster than a fully-fledged API server, and is worth considering even in cloud testing environments. All in all, the benefits of using mock servers are pretty compelling, and help you to reduce one of the most common types of instability in your test suite--relying on remote or 3rd-party dependencies in the form of external services.