In this article I'll be talking about the Mountebank open-source service virtualization tool and how you can use it to make things easier when developing a mobile application.

We recently experimented with it for a client who needed to make a change to a native mobile application in order to match a functionality change in the server Application Programming Interface (API). Since the functionality involved payments, we needed to make it easy for mobile developers to conduct front-end changes without causing undesirable side effects. Setting up a custom development environment was not possible as the end-to-end flow was split between three different organizations. However, we were able to create a partially-mocked web service locally on the mobile developers' laptops using the open-source tool Mountebank.

It's Called What?

The word "mountebank" is a delightful old word that refers to salespeople who used to go from town to town selling snake oil as a "miracle cure." I also like how you can use the word as a verb.

moun·te·bank

(noun) a person who sells quack medicines from a platform
(noun) a boastful unscrupulous pretender : charlatan
(verb) to beguile or transform by trickery

source: Merriam-Webster

Our goal here is to trick our client app just well enough to achieve integration-test functionality, without having to set up all the infrastructure for a whole integrated development environment.

Installation

To get started with Mountebank, you'll need to have Node.js installed, along with its packager, npm. If you're running a MacBook, I'd recommend bootstrapping with Homebrew, and then running brew install node. If you're running Windows, you'll probably need to manually download an installer. Once Node.js is installed, you can install Mountebank globally using using npm.

npm install -g mountebank

This will install Mountebank and its dependencies into a central location. It's also possible to configure Mountebank to run locally within a project folder using a package.json, but that's beyond the scope of this article.

Once it's installed, you can start the service by running it from the command line.

mb

By default Mountebank can be configured using HTTP POSTs to the running instance, but I like to work with an imposters.ejs (JSON format) configuration file and reload as needed.

mb --configfile imposters.ejs

You can stop the server using ctrl-C, or run mb stop in another window.

With the service running, you can view the console by loading it in a browser at http://localhost:2525.

The configuration file is JSON. The *.ejs extension includes some features that let you do things like inline HTML or include other files. If your application is REST-based and not too complicated, you probably won't need these features. I like to edit the file using Microsoft's Visual Studio Code, with a plugin to escape nested JSON.

Configuration

Mountebank uses a straightforward hierarchical model that is easy to understand in the JSON.

Imposters (note the spelling) are services that listen on a port for HTTP requests. An imposter consists of one or more stubs, each of which consists of one or more predicates, and one or more responses.

Simple Example

Let's start with a simple example. Say that we have a contrived API with an endpoint that fetches the current user's profile.

 {
 "imposters": [
 {
 "port": 5555,
 "protocol": "http",
 "name": "my-simple-api",
 "recordRequests": true,
 "stubs": [
 {
 "predicates": [
 { "equals": { "method": "GET", "path": "/me" }}
 ],
 "responses": [
 {
 "is": {
 "statusCode": 200,
 "headers": { "Content-Type": "application/json" },
 "body": "{\"givenName\":\"Dev\",\"surname\": \"Loper\",\"city\":\"Richmond\"}"
 }
 }
 ]
 }
 ]
 }
 ]
 }

It's pretty easy to understand what this example does. When your client issues an HTTP GET to localhost:5555/me, Mountebank will respond with a 200 status code and a small JSON structure. Since the JSON response is nested within the JSON configuration file, you'll need to escape the quotation marks, but that's simple to do with an editor plugin.

You can try this out using a browser, or an API testing tool such as Postman.

Note that we're not doing anything here to check the user's authentication. We're basically just saying, Whenever you receive a GET request matching the criteria, here's how you should respond.

How about an example that uses PUT to update the user's profile:

 {
 "predicates": [
 { "equals": { "method": "PUT", "path": "/me" }}
 ],
 "responses": [
 {
 "is": {
 "statusCode": 400,
 "headers": { "Content-Type": "application/json" },
 "body": "{\"errors\":[{\"city\":\"Must not be blank\"}]}"
 },
 {
 "is": {
 "statusCode": 200,
 "headers": { "Content-Type": "application/json" },
 "body": "{\"givenName\":\"Dev\",\"surname\": \"Loper\",\"city\":\"Philadelphia\"}"
 }
 ]
 }

Here, we've created a stub with two responses in it: The first will return a 400 code with a validation error, and the second will return a 200 status code and include a change to the city. The responses array represents a looping sequence, so Mountebank will alternate between these two responses and allow you to test both scenarios.

By now your mind is probably racing with questions, such as:

How do I get it to spit back the same content that I've POSTed?
How do I validate request data?
How do I manage state?
How do I get it to run some code?

The answer is: You don't. Mountebank is a parrot. It's intended to repeat the things you tell it to do, without understanding any of it. Building this kind of logic is the task of the server-side developer. Our job is to prop up a façade that's just sophisticated enough to fool the client code.

Proxies

Suppose that you want to mock out only a part of the API; for example, the part of the API dealing with user account data or even just a single endpoint. Meanwhile, the rest of the API deals with public data and you'd like to just use whatever the live API is serving. Mountebank lets you configure a stub with a proxy.

 {
 "predicates": [
 { "equals": { "method": "PUT", "path": "/me" }}
 ],
 "responses": [
 {
 "is": {
 "statusCode": 400,
 "headers": { "Content-Type": "application/json" },
 "body": "{\"errors\":[{\"city\":\"Must not be blank\"}]}"
 },
 {
 "is": {
 "statusCode": 200,
 "headers": { "Content-Type": "application/json" },
 "body": "{\"givenName\":\"Dev\",\"surname\": \"Loper\",\"city\":\"Philadelphia\"}"
 }
 ]
 }

This will forward your request to the proxy destination, capture the output, and return the result as its own. We can set the mode to proxyAlways to have Mountebank forward every request, or proxyOnce to only do so the first time and service successive requests from cache.

One thing to note here is that the proxy destination cannot include a path, only a scheme and hostname. So for example, if your production API URL has endpoints like "https://api.example.com/api/v1/products", you can't configure Mountebank to use a base path of "https://api.example.com/api/v1". You'll need to include the "/api/v1" in each of your stubs.

When you navigate your browser to the console on port 2525, you can see the recorded requests and their results in the list of imposters. Mountebank can save these captured records for you via the `mb save` command, and then replay them later.

You can set this proxy stub as last in the sequence, without a predicate. Mountebank will evaluate the predicates for each of the stubs in order, so if your last one doesn't have one, it will be used as a fallback.

Configuring Your Client Mobile Application

Once you've got your mock API running, you'll need to point your mobile application to it. Make sure to point the app at your imposter's configured port, not the main Mountebank port of 2525.

On iOS, you'll typically keep your API URLs in a configuration file such as Info.plist. You will probably also need to set the NSAppTransportSecurity > Allow Arbitrary Loads (a.k.a. NSAllowsArbitraryLoads) key to YES in order to allow your app to fetch data from localhost via unencrypted HTTP. Obviously you will need to take this out before submitting your build to the App Store, so you may want to configure XCode to only do this for development builds.

On Android, you may have your network endpoints configured in a properties or YAML file, or in an enum or constants class. In any of these cases, you will likely need to use an IP address of 10.0.2.2 instead of localhost to run your code in the emulator.

To run your code on a physical device, either tethered or untethered, you'll need to use a real IP address or hostname. For development it would be preferable to only use one that's accessible over the same local WiFi network.

Conclusion

Mountebank is versatile and lightweight enough to run on your laptop, and easy enough to configure. As such, I suggest including it in your toolkit whenever you need to simulate or debug challenging use cases in client-server mobile applications.