bbyars/mountebank

View on GitHub
src/views/docs/api/predicates.ejs

Summary

Maintainability
Test Coverage
<%
title = 'predicates'
description = 'The predicates supported by mountebank'
%>

<%- include('../../_header') -%>

<h1>Predicates</h1>

<p>In the absence of a predicate, a stub always matches, and there's never a reason to
add more than one stub to an imposter.  Predicates allow imposters to have much richer
behavior by defining whether or not a stub matches a request.  When multiple stubs are
created on an imposter, the first stub that matches is selected.</p>

<p>Each predicate object contains one or more of the request fields as keys.  Predicates
are added to a stub in an array, and all predicates are AND'd together.  The following
predicate operators are allowed:</p>

<table>
  <tr>
    <th>Operator</th>
    <th>Description</th>
  </tr>
  <tr>
    <td><code>equals</code></td>
    <td>The request field matches the predicate</td>
  </tr>
  <tr>
    <td><code>deepEquals</code></td>
    <td>Performs nested set equality on the request field, useful when
    the request field is an object (e.g. the <code>query</code> field in http)</td>
  </tr>
  <tr>
    <td><code>contains</code></td>
    <td>The request field contains the predicate</td>
  </tr>
  <tr>
    <td><code>startsWith</code></td>
    <td>The request field starts with the predicate</td>
  </tr>
  <tr>
    <td><code>endsWith</code></td>
    <td>The request field ends with the predicate</td>
  </tr>
  <tr>
    <td><code>matches</code></td>
    <td>The request field matches the JavaScript regular expression defined
    with the predicate.</td>
  </tr>
  <tr>
    <td><code>exists</code></td>
    <td>If <code>true</code>, the request field must exist.  If <code>false</code>,
    the request field must not exist.</td>
  </tr>
  <tr>
    <td><code>not</code></td>
    <td>Inverts a predicate</td>
  </tr>
  <tr>
    <td><code>or</code></td>
    <td>Logically or's two predicates together</td>
  </tr>
  <tr>
    <td><code>and</code></td>
    <td>Logically and's two predicates together</td>
  </tr>
  <tr>
    <td><code>inject</code></td>
    <td>Injects JavaScript to decide whether the request matches or not.
    See the <a href='/docs/api/injection'>injection</a> page for more details.</td>
  </tr>
</table>

<p>Predicates can be parameterized.  mountebank accepts the following
predicate parameters:</p>

<table>
  <tr>
    <th>Parameter</th>
    <th>Default</th>
    <th>Description</th>
  </tr>
  <tr>
    <td><code>caseSensitive</code></td>
    <td><code>false</code></td>
    <td>Determines if the match is case sensitive or not.  This includes keys
    for objects such as query parameters.</td>
  </tr>
  <tr>
    <td><code>except</code></td>
    <td><code>""</code></td>
    <td>Defines a regular expression that is stripped out of the request field
    before matching.</td>
  </tr>
  <tr>
    <td><code>xpath</code></td>
    <td><code>null</code></td>
    <td>Defines an object containing a <code>selector</code> string and, optionally, an
    <code>ns</code> object field that defines a namespace map.  The predicate's
    scope is limited to the selected value in the request field.</td>
  </tr>
  <tr>
    <td><code>jsonpath</code></td>
    <td><code>null</code></td>
    <td>Defines an object containing a <code>selector</code> string.  The predicate's
    scope is limited to the selected value in the request field.</td>
  </tr>
</table>

<p>See the <code>equals</code> example below to see the <code>caseSensitive</code> and
<code>except</code> parameters in action.  See the <a href='/docs/api/xpath'>xpath page</a>
for <code>xpath</code> examples. See the <a href='/docs/api/jsonpath'>jsonpath page</a>
for <code>jsonpath</code> examples.</p>

<p>Almost all predicates are scoped to a request field; see the protocol pages linked to from
the sidebar to see the request fields.  <code>inject</code> is the sole exception.  It takes
a string function that accepts the entire request.  See the
<a href='/docs/api/injection'>injection</a> page for details.</p>

<p>The predicates work intuitively for base64-encoded binary data as well by internally
converting the base64-encoded string to a JSON string representing the byte array.  For example,
sending "AQIDBA==" will get translated to "[1,2,3,4]", and predicates expecting "AgM=" will
get translated to "[2,3]".  Even though "AQIDBA==" does not contain "AgM=", a <code>contains</code>
predicate will match, because "[1,2,3,4]" does contain "[2,3]".  This works well for everything
but <code>matches</code>, because any regular expression operators get encoded as binary.
mountebank recommends that you stay away from <code>matches</code> if you're dealing in binary.
In mountebank's experience, <code>contains</code> is the most useful predicate for binary
imposters, as even binary RPC data generally contains strings representing method names.</p>

<h2 id='array-match'>Matching arrays</h2>

<p>On occasion you may encounter multi-valued keys.  This can be the case with querystrings
and HTTP headers that have repeating keys, for example <code>?key=first&amp;key=second</code>.
In those cases, <code>deepEquals</code> will require all the values (in any order) to match.
All other predicates will match if any value matches, so an <code>equals</code> predicate
will match with the value of <code>second</code> in the example above.</p>

<p><a href='/docs/api/json'>JSON predicates</a> are also allowed, for example, to match HTTP
bodies. When the body contains an array, the logic above still applies: <code>deepEquals</code>
requires all values to match, and other predicates will match if any value in the array
matches.</p>

<p>You also have the option of specifying an array in the predicate definition. If you do so,
then <em>all</em> fields in the predicate array must match (in any order). Most predicates
will match even if there are additional fields in the actual request array; <code>deepEquals</code>
requires the array lengths to be equal. The following example shows this with a querystring:</p>

<testScenario name='array'>
    <step type='http'>
<pre><code>POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json

{
  "port": 3333,
  "protocol": "http",
  "stubs": [
    {
      "predicates": [{
        "<strong class='highlight1'>deepEquals</strong>": {
          "query": { "key": <strong class='highlight1'>["first", "second"]</strong> }
        }
      }],
      "responses": [{
        "is": {
          "body": <strong class='highlight1'>"Entire array matched"</strong>
        }
      }]
    },
    {
      "predicates": [{
        "<strong class='highlight2'>equals</strong>": {
          "query": { "key": <strong class='highlight2'>["first", "second"]</strong> }
        }
      }],
      "responses": [{
        "is": {
          "body": <strong class='highlight2'>"Subset of array matched"</strong>
        }
      }]
    },
    {
      "predicates": [{
        "equals": {
          "query": { "key": <strong class='highlight3'>"first"</strong> }
        }
      }],
      "responses": [{
        "is": {
          "body": <strong class='highlight3'>"A field in the array matched"</strong>
        }
      }]
    }
  ]
}</code></pre>
    </step>

<p>First let's call the imposter matching both keys in the <code>deepEquals</code> predicate. For it
to match, no other keys must be present, although the order does not matter.</p>

    <step type='http'>
<pre><code>GET /path<strong class='highlight1'>?key=second&key=first</strong> HTTP/1.1
Host: localhost:3333</code></pre>

<p>Since both keys match and there are no extraneous keys, we get our expected response from the first stub.</p>

        <assertResponse>
<pre><code>HTTP/1.1 200 OK
Connection: close
Date: <volatile>Sat, 06 May 2017 02:30:31 GMT</volatile>
Transfer-Encoding: chunked

<strong class='highlight1'>Entire array matched</strong></code></pre>
        </assertResponse>
    </step>

<p>If we add a third key not specified by the predicate, it no longer matches the <code>deepEquals</code>
predicate. It does, however, match the <code>equals</code> predicate, which supports matching a subset of arrays.</p>

    <step type='http'>
<pre><code>GET /path<strong class='highlight2'>?key=second&key=first</strong>&key=third HTTP/1.1
Host: localhost:3333</code></pre>

<p>Since both keys match, we get our expected response from the first stub.</p>

        <assertResponse>
<pre><code>HTTP/1.1 200 OK
Connection: close
Date: <volatile>Sat, 06 May 2017 02:30:31 GMT</volatile>
Transfer-Encoding: chunked

<strong class='highlight2'>Subset of array matched</strong></code></pre>
        </assertResponse>
    </step>

<p>If the request is missing either array value specified in the predicate, it no longer matches.</p>

    <step type='http'>
<pre><code>GET /path?<strong class='highlight3'>key=first</strong>&key=third HTTP/1.1
Host: localhost:3333</code></pre>

<p>In this case, our third stub matches, because it does <em>not</em> use an array predicate, and
one of the actual array values in the request matches the predicate definition</p>

        <assertResponse>
<pre><code>HTTP/1.1 200 OK
Connection: close
Date: <volatile>Sat, 06 May 2017 02:30:31 GMT</volatile>
Transfer-Encoding: chunked

<strong class='highlight3'>A field in the array matched</strong></code></pre>
        </assertResponse>
    </step>

    <step type='http'>
<code class='hidden'>DELETE /imposters/3333 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json</code>
    </step>
</testScenario>

<h2>Matching XML or JSON</h2>

<p>mountebank has special support for matching XML and JSON request fields, such as in an <code>http body</code>
or <code>tcp data</code> field.  Where XML or JSON predicates are used against <code>string</code> fields,
mountebank will attempt to parse the field as XML or JSON and apply the given predicate.  If he is unable to
parse the field, the predicate will fail; otherwise it will pass or fail according to the selected value.</p>

<p>See the <a href='/docs/api/xpath'>xpath page</a> for <code>xpath</code> examples.</p>
<p>See the <a href='/docs/api/json'>json page</a> for <code>json</code> examples.</p>

<h2>Examples</h2>

<p>The examples below use both HTTP and TCP imposters.  The TCP examples use netcat (<code>nc</code>)
to send TCP data over a socket, which is like <code>telnet</code>, but makes the output easier to script.
The examples for binary imposters use the <code>base64</code> command line tool to decode base64
to binary before sending to the socket.</p>

<section class='accordion'>
  <div>
    <a class='section-toggler'
       id='predicates-equals' name='predicates-equals' href='#predicates-equals'>
      equals
    </a>
    <section>
      <%- include('predicates/equals') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-deepEquals' name='predicates-deepEquals' href='#predicates-deepEquals'>
      deepEquals
    </a>
    <section>
      <%- include('predicates/deepEquals') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-contains' name='predicates-contains' href='#predicates-contains'>
      contains
    </a>
    <section>
      <%- include('predicates/contains') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-startsWith' name='predicates-startsWith' href='#predicates-startsWith'>
      startsWith
    </a>
    <section>
      <%- include('predicates/startsWith') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-endsWith' name='predicates-endsWith' href='#predicates-endsWith'>
      endsWith
    </a>
    <section>
      <%- include('predicates/endsWith') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-matches' name='predicates-matches' href='#predicates-matches'>
      matches
    </a>
    <section>
      <%- include('predicates/matches') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-exists' name='predicates-exists' href='#predicates-exists'>
      exists
    </a>
    <section>
      <%- include('predicates/exists') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-not' name='predicates-not' href='#predicates-not'>
      not
    </a>
    <section>
      <%- include('predicates/not') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-or' name='predicates-or' href='#predicates-or'>
      or
    </a>
    <section>
      <%- include('predicates/or') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-and' name='predicates-and' href='#predicates-and'>
      and
    </a>
    <section>
      <%- include('predicates/and') -%>
    </section>
  </div>
  <div>
    <a class='section-toggler'
       id='predicates-inject' name='predicates-inject' href='#predicates-inject'>
      inject
    </a>
    <section>
      <%- include('predicates/inject') -%>
    </section>
  </div>
</section>

<%- include('../../_footer') -%>