From Testing Microservices with Mountebank by Brandon Byars

This article discusses a unique kind of service testing – testing a service that depends on a fictional “Abagnale” service. It involves doing something which has probably never been done before in the history of mocking frameworks: we need to create a virtual imposter that pretends to be a real imposter.


Save 37% on Testing Microservices with Mountebank. Just enter code fccbyars into the discount code box at checkout at manning.com.


 

During his younger years, Frank William Abagnale, Jr forged a pilot’s license and traveled the world by deadheading (The term refers to a pilot riding as a passenger on a flight to get to work). Despite being unable to fly, his successful impersonation of a pilot meant that his food and lodging were fully paid for by the airline. When that well dried up, he impersonated a physician in New Orleans for nearly a year without any medical background, supervising resident interns. When he didn’t know how to respond after a nurse said a baby had “gone blue,” he realized the life and death implications of that false identity and decided to make yet another change. After forging a law transcript from Harvard University, he kept taking the Louisiana bar exam until he passed it, and posed as an attorney with the Attorney General’s office. His story was memorialized in the hit movie Catch Me If You Can.

Frank Abagnale was one of the most successful imposters of all time.

Although mountebank can’t guarantee you Abagnale’s indefatigable confidence, it provides you the ability to mimic one of the other key factors of his success: tailoring your response to your audience. Had Abagnale acted like a pilot to a room full of medical interns, he’d have had a much shorter medical career. Mountebank uses predicates to determine which response to use based on the incoming request, giving your imposter the ability to pretend to be a virtual pilot for one request and a virtual doctor for the next one.

The basics of predicates

Testing a service that depends on a fictional Abagnale service is hard work.


Figure 1. The Abagnale service adapts its response to the questions asked of it


Fortunately, mountebank makes this easy. If we assume that our system embeds the question inside the HTTP body, then our imposter configuration can look something like this:


Listing 1. Creating an Abagnale imposter

{
  "protocol": "http",
  "port": 3000,
  "stubs": [
    {
      "predicates": [{
        "contains": { "body": "Which route are you flying?" } ❶
      }],
      "responses": [{
        "is": { "body": "Miami to Rio" } ❷
      }]
    },
    {
      "predicates": [{
        "startsWith": { "body": "Where did you get your degree?" } ❸
      }],
      "responses": [{
        "is": { "body": "Harvard Medical School" } ❹
      }]
    },
    {
      "responses": [{
        "is": { "body": "I'll have to get back to you" } ❺
      }]
    }
  ]
}

 

When asked a pilot question…

…respond like a pilot

When asked a doctor question…

…respond like a doctor

If you don’t know what type of question it is, stall!


Our simple Abagnale imposter shows more possibilities than an equals predicate by using contains and startsWith. We’ll look at the range of predicates shortly, but most of them are self-explanatory. If the body contains the text “Which route are you flying?” then our imposter responds with “Miami to Rio,” and if the body starts with the text “Where did you get your degree?” our imposter responds with “Harvard Medical School.” This allows us to test the Doctor and Pilot services without depending on the full talents of Mr. Abagnale.

Note the last stub, containing the “I’ll have to get back to you” bit of misdirection. It contains no predicates, which means that all requests will match it, including requests that match the predicates in other stubs. Because a request can match multiple stubs, mountebank always picks the first match, based on array order. This allows you to represent a default fallback response.

We haven’t paid too much attention to stubs as a standalone concept. This is because stubs only make sense in the presence of predicates. It’s possible to send different responses to the exact same request, which is why the responses field is a JSON array. This simple fact, combined with the need to tailor the response to the request, is the raison d’etre of stubs. Each imposter contains a list of stubs. Each stub contains a circular buffer for the responses. Mountebank selects which stub to use based on the stub’s predicates.


Figure 2. Mountebank matches the request against each stub’s predicates


Because mountebank uses a “first-match” policy on the stubs, there’s no problem having multiple stubs that could respond to the same request.

Types of predicates

Let’s take a closer look at the simplest predicate operators:


Figure 3. How simple predicates match against the full request field


These all behave much as you’d expect. Several other interesting types of predicates exist, starting with the incredibly useful matches predicate.

THE MATCHES PREDICATE

Our Abagnale service needs to respond intelligently to questions both in the present tense and the past tense. “Which route are you flying?” and “Which route did you fly?” should both trigger a response of “Miami to Rio.” We could write multiple predicates, but the matches predicate lets us simplify our configuration with the use of a regular expression.


Figure 4. Mountebank matches the request against each stub’s predicates


Regular expressions include a wonderfully rich set of metacharacters to simplify pattern matching. In this example, we use three:

  • . – matches any character except a newline
  • * – matches the previous character zero or more times
  • \ – escapes the following character, matching it literally

It looks like we had to double-escape the question mark, but this is because “\” is a JSON string escape character as well as a regular expression escape character. The first “\” JSON-escapes the second “\”, which regex-escapes the question mark because it turns out that a question mark is also a special metacharacter. Like the asterisk, it sets an expectation on the previous character or expression, but unlike the * metacharacter, the question mark matches it only zero or one time. If you’ve never seen regular expressions before, it’s all a bit confusing; let’s break down our pattern against the request field value of “Which route did you fly?”


Figure 5. How a regular expression matches against a string value


Most characters match one-for-one against the request field. As soon as we reach the first metacharacters (.*), the pattern matches as much as it can until the next literal character (a blank space). The wildcard pattern .* is a simple way of saying “I don’t care what they entered.” If the rest of the pattern matches, then the entire regular expression matches.

The second .* matches because the * allows a zero-character match, which is another way of saying it can be satisfied even if it doesn’t match anything. Conveniently, it also would have matched the “ing” if we’d entered “flying” instead of “fly,” which is why regular expressions are flexible. Finally, the pattern \? matches the ending question mark.

The matches predicate is one of the most versatile predicates in mountebank. With a couple additional metacharacters, it can completely replace other predicates.

That’s all for this article.


For more, check out the whole book on liveBook here and see this Slideshare Presentation.