bbyars/mountebank

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

Summary

Maintainability
Test Coverage
<%
title = 'jsonpath'
description = 'Using JsonPath in mountebank predicates'
%>

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

<h1>Using JSONPath</h1>

<p>It is common for mountebank to see JSON documents in his line of business, and he suspects
    the same may be true for you.  With the goal of making JSON easier to work with, mountebank
    accepts a <code>jsonpath</code> predicate parameter.  This parameter narrows the scope of the predicate
    value to a value matched by the jsonpath selector, much like the <a href='/docs/api/predicates'>
        <code>except</code> parameter</a>.  mountebank suggests avoiding using this parameter on fields
    that are not JSON documents.  He also counsels avoiding the temptation to use it with binary protocols.</p>

<p>mountebank uses the following field with jsonpath:</p>

<table>
    <tr>
        <th>Field</th>
        <th>Required?</th>
        <th>Description</th>
    </tr>
    <tr>
        <td><code>selector</code></td>
        <td>Yes</td>
        <td>The JSONPath selector</td>
    </tr>
</table>

<p>You can send the parameter:</p>

<pre><code>
{
    "equals": {
        "body": "Catcher in the Rye"
    },
    "jsonpath": {
        "selector": "$..title"
    }
}
</code></pre>

<p>The JSONPath parameter follows the same semantics as those obeyed for multi-valued keys described on the
    <a href='/docs/api/predicates'>main predicates page</a>, like those observed when a querystring has
    the same key multiple times.  Since the JSONPath selector can potentially match multiple nodes in the JSON
    document, this is an important point to make.  <code>deepEquals</code> will require all the values to match
    (although the order isn't important).  All other predicates will match if any node value matches.  The examples below
    explore these semantics.</p>

<h2>Examples</h2>

<p>Let's create an HTTP imposter with multiple stubs.  We'll use redundant predicates simply to
    demonstrate various ways to use the <code>jsonpath</code> parameter.  Where applicable, a
    <code>comment</code> field is added to the JSON to explain what's going on.  Like all superfluous
    fields, mountebank will ignore it.</p>

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

{
    "port": 4545,
    "protocol": "http",
    "stubs": [<strong class='highlight1'>{
        "responses": [{ "is": { "body": "Basic jsonpath usage" } }],
        "predicates": [
          {
            "equals": { "body": "Catcher in the Rye" },
            "jsonpath": { "selector": "$..title" },
            "caseSensitive": true,
            "comment": "case sensitivity applies to the selector as well as the value"
          },
          {
            "equals": { "body": "RYE" },
            "jsonpath": { "selector": "$..TITLE" },
            "except": "CATCHER IN THE ",
            "comment": "The except regular expression is removed from the value before matching"
          },
          {
            "matches": { "body": "^Catcher" },
            "jsonpath": { "selector": "$..title" }
          },
          {
            "exists": { "body": true },
            "jsonpath": { "selector": "$..title" },
            "comment": "body must exist (e.g. be empty) AND the JSONPath must match at least one node"
          },
          {
            "exists": { "body": false },
            "jsonpath": { "selector": "$..publisher" },
            "comment": "body must not exist (e.g. be empty) OR the JSONPath must not match any nodes"
          }
        ]
    }</strong>,<strong class='highlight2'>{
        "responses": [{
            "is": { "body": "JSONPath with attributes and the quirks of deepEquals" }
        }],
        "predicates": [
          {
            "deepEquals": { "body": "Robert Cecil Martin" },
            "jsonpath": { "selector": "$.book[1].author" },
            "comment": "deepEquals expects a string when there is only one match"
          },
          {
            "deepEquals": { "body": ["J. K. Rowling", "Robert Cecil Martin", "Dr. Seuss"] },
            "jsonpath": { "selector": "$.book..author" },
            "comment": "Use an array for deepEquals when there are multiple matches"
          }
        ]
    }</strong>]
}</code></pre>
    </step>

<p>The first predicate uses an jsonpath selector to navigate to all <code>title</code> elements
    within the JSON within the <code>body</code> field.  If <code>body</code> turns out not
    to be an JSON, none of them will match.  The <code>caseSensitive</code> parameter applies
    both to the predicate value in <code>body</code> and to the <code>selector</code> field in
    <code>jsonpath</code>.  The <code>exists</code> predicate behavior is identical to its behavior with
    other fields, except now the jsonpath selector must also match if <code>exists</code> is <code>true</code>,
    or not match if <code>exists</code> is false.<p>

<p>Note that in the example below, there are multiple nodes matching the jsonpath selector.  With all
    predicates except <code>deepEquals</code>, it's sufficient that <i>any</i> of them match.</p>

    <step type='http'>
<pre><code>POST / HTTP/1.1
Host: localhost:4545

{
    "book":[
      {
        "title": "Game of Thrones",
        "isbn:summary": "Dragons and political intrigue",
        "author": "George R.R. Martin"
      },
      {
        <strong class='highlight1'>"title": "Catcher in the Rye"</strong>,
        "isbn:summary": "It is a book",
        "author": "J. D. Salinger"
      },
      {
        "title": "Notes from the Underground",
        "isbn:summary": "The world's first existentialist novel",
        "author":"Fyodor Dostoyevsky"
      }
    ]
}</code></pre>

        <assertResponse>
<pre><code>HTTP/1.1 200 OK
Connection: close
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
Transfer-Encoding: chunked

<strong class='highlight1'>Basic jsonpath usage</strong></code></pre>
        </assertResponse>
    </step>

<p>To test the second stub, we'll use deep equals to get a specific <code>title</code>
node dependant on what book we are looking at. We can also use deep equals to get
all the corresponding nodes.</p>

    <step type='http'>
<pre><code>POST / HTTP/1.1
Host: localhost:4545

{
    "book":[
      {
        <strong class='highlight2'>"title": "Harry Potter"</strong>,
        "isbn:summary": "Wizards and Magic",
        "author": "J. K. Rowling"
      },
      {
        "title": "Clean Code",
        "isbn:summary": "Technological bible",
        "author": "Robert Cecil Martin"
      },
      {
        "title": "The Cat in the Hat",
        "isbn:summary": "Childhood classic",
        "author":"Dr. Seuss"
      }
    ]
}</code></pre>

        <assertResponse>
<pre><code>HTTP/1.1 200 OK
Connection: close
Date: <volatile>Thu, 09 Jan 2014 02:30:31 GMT</volatile>
Transfer-Encoding: chunked

<strong class='highlight2'>JSONPath with attributes and the quirks of deepEquals</strong></code></pre>
        </assertResponse>
    </step>

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

</testScenario>

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