<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jeff Farley</title>
    <description>The latest articles on DEV Community by Jeff Farley (@jeffreymfarley).</description>
    <link>https://dev.to/jeffreymfarley</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4021%2F8754176.jpeg</url>
      <title>DEV Community: Jeff Farley</title>
      <link>https://dev.to/jeffreymfarley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeffreymfarley"/>
    <language>en</language>
    <item>
      <title>Mocking Elasticsearch</title>
      <dc:creator>Jeff Farley</dc:creator>
      <pubDate>Wed, 31 Jan 2018 13:27:25 +0000</pubDate>
      <link>https://dev.to/jeffreymfarley/mocking-elasticsearch-5goj</link>
      <guid>https://dev.to/jeffreymfarley/mocking-elasticsearch-5goj</guid>
      <description>&lt;p&gt;Let me know if this sounds familiar...&lt;/p&gt;

&lt;h3&gt;
  
  
  It starts off so innocently...
&lt;/h3&gt;

&lt;p&gt;After reading the Elasticsearch tutorial, I quickly put together a block of code that sends a simple string and gets back a load of useful data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuoamc7o3d5i0406azcia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuoamc7o3d5i0406azcia.png" alt="How easy is this?"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deeper down the rabbit hole
&lt;/h3&gt;

&lt;p&gt;Immediately I noticed the location of the "real" data.  It's buried way down there in &lt;code&gt;hits.hits._source&lt;/code&gt;.  So I updated the block of code to extract the useful array from the noisy metadata.&lt;/p&gt;

&lt;p&gt;As I continued to develop, subtle searching distinctions started appearing and needed to be handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handling wildcards or special symbols in the user's search&lt;/li&gt;
&lt;li&gt;Do I use &lt;code&gt;query_string&lt;/code&gt; or &lt;code&gt;match&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;term&lt;/code&gt; vs &lt;code&gt;terms&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;must&lt;/code&gt; vs &lt;code&gt;should&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And even if the correct search is identified, there are other features that should be part of a real application, like aggregations and highlighting, which lead to more concepts like &lt;code&gt;post_filter&lt;/code&gt; and &lt;code&gt;.raw&lt;/code&gt; fields.  What was originally quite simple is starting to look more like a hairball.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rqpnzfxlokgl6vnl9ww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rqpnzfxlokgl6vnl9ww.png" alt="Can't sleep, clowns will eat me"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I know, I'll create a QueryBuilder
&lt;/h3&gt;

&lt;p&gt;Eventually, I wanted to get back to the simplicity that existed at the beginning.  The best course of action was to create a module that accepts a few parameters, build the big query and process the results for return. It's a good case for modular development and I can use tests to verify it works correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9nyae4yq3m1jsxukdjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9nyae4yq3m1jsxukdjt.png" alt="Looks Good"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now there are some tough questions to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In order to test this code, does every developer have access to the same Elasticsearch instance as I do?  What about the CI server?&lt;/li&gt;
&lt;li&gt;It's easy to set up a test passing parameters to the module (1), but how to set up a spy at (2) to verify the hairball?&lt;/li&gt;
&lt;li&gt;Setting up the assertion at (4) is even worse.  In the rush to quit thinking about it, I might have wrote a test that looked like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is almost as bad as no test at all.  And to be a little pedantic, these aren't even unit tests, these are systems integration tests.  The scope is too big for practical development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taking a step back
&lt;/h3&gt;

&lt;p&gt;The second or third time I went through this process, I started to think about a better approach.  The key is the CI server.  I don't want Jenkins, Travis or CircleCI to hit a live instance.  In the case of PII, it may not be allowed to access the data anyway.  I want to have something that looks like Elasticsearch but is static and deterministic.&lt;/p&gt;

&lt;p&gt;Putting on my functional programming hat, it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh1tat56vp1cay9vckk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh1tat56vp1cay9vckk9.png" alt="black box"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A set of params (the circle) produces a pair of shapes, the request and the response.  By enumerating the parameters to be tested, I can produce the pair of shapes for each test.  That's a good theory, but how to put into practice?  I need to figure out how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enumerate the set of parameters&lt;/li&gt;
&lt;li&gt;Store the request/response pairs&lt;/li&gt;
&lt;li&gt;Capture the request that goes with each set of parameters&lt;/li&gt;
&lt;li&gt;Capture the response&lt;/li&gt;
&lt;li&gt;Load the pairs into the tests&lt;/li&gt;
&lt;li&gt;Automate this process&lt;/li&gt;
&lt;li&gt;Detect when Elasticsearch changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot, so let's get started:&lt;/p&gt;

&lt;h3&gt;
  
  
  Enumerating the set of parameters
&lt;/h3&gt;

&lt;p&gt;If time and money were in infinite supply, we could exhaustively test every combination of parameters to ensure all possibilities are covered.  Since we don't live in that reality, code coverage metrics are the next best thing.  The first step is to create tests that cover the percentage of code you feel comfortable with.  Don't worry about good assertions at this point, we are just looking to make sure all code paths are hit. &lt;/p&gt;

&lt;h3&gt;
  
  
  Preparing to store the request/response pairs
&lt;/h3&gt;

&lt;p&gt;I use the following directory hierarchy to guide where each request/response pair goes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+- &amp;lt;API root&amp;gt;
  +- &amp;lt;module&amp;gt;
    +- __mocks__
      +- &amp;lt;index-name&amp;gt;
        +- &amp;lt;endpoint&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directory&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;API Root&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;The root of the application.&lt;/td&gt;
&lt;td&gt;bookstore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;module&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;A REST endpoint&lt;/td&gt;
&lt;td&gt;books, orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__mocks__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A convention from &lt;a href="https://facebook.github.io/jest/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; I liked&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;index-name&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;The Elasticsearch index&lt;/td&gt;
&lt;td&gt;sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;endpoint&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;The Elasticsearch endpoint.&lt;/td&gt;
&lt;td&gt;_search, _suggest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Create a Fake to encapsulate the behavior
&lt;/h3&gt;

&lt;p&gt;With the directory structure defined, I'll create a class that hides some of the gory details. This should be located at the root of the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeElasticsearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;short_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;subdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_search&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;short_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;subdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__mocks__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;endpoint&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buildPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_req.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_resp.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_req.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Capturing the existing request (Method #1 - Server-side)
&lt;/h3&gt;

&lt;p&gt;Next step is finding the code that calls Elasticsearch, and temporarily inserting a few lines that save off the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="nd"&gt;@api_view&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;short_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;

    &lt;span class="bp"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SAVE_REQUESTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeElasticsearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;short_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Capturing the existing request (Method #2 - Browser-based)
&lt;/h3&gt;

&lt;p&gt;When the code that builds the query and calls Elasticsearch runs inside a browser, capturing the request gets a little more complex.  Basically, there are a few options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;code&gt;Network&lt;/code&gt; tab in developer tools to manually copy the request object and paste it into a JSON file in the correct location.  This is a good option to start with, since it requires no extra code, but quickly gets unwieldy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;a href="http://www.elasticsearch.org/guide/reference/index-modules/slowlog/" rel="noopener noreferrer"&gt;"slow log"&lt;/a&gt; feature of Elasticsearch to capture the queries it is receiving.  Then search through the logs and save off the useful requests to a JSON file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quickly create an API endpoint and post the request there.  This has all the advantages of the server-side version but does incur additional development time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Automatically capture the response
&lt;/h3&gt;

&lt;p&gt;I let &lt;a href="https://curl.haxx.se/docs/manpage.html" rel="noopener noreferrer"&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/a&gt; do the grunt work of hitting the Elasticsearch instance and capturing the response.  The raw response contains some metadata that will be different between runs, like &lt;code&gt;took&lt;/code&gt;, so it needs to be stripped out.  I used &lt;a href="https://stedolan.github.io/jq/" rel="noopener noreferrer"&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/a&gt;, but &lt;code&gt;awk&lt;/code&gt; or &lt;code&gt;sed&lt;/code&gt; will work just as well.&lt;/p&gt;

&lt;p&gt;Also, if you are a beginner with *nix like I am, the construct &lt;code&gt;${REQ%_req.*}&lt;/code&gt; looks like it does nothing, but it is &lt;a href="http://tldp.org/LDP/abs/html/string-manipulation.html" rel="noopener noreferrer"&gt;shell string manipulation&lt;/a&gt; and very handy to know.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.example.org:9200"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;REQ &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*_req.json"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REQ&lt;/span&gt;&lt;span class="p"&gt;%_req.*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;RESPONSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;_resp.json
  &lt;span class="nv"&gt;INDEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PREFIX&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PREFIX&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="nt"&gt;-f5&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"_suggest"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nv"&gt;JQ_PROCESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{sgg}"&lt;/span&gt;
  &lt;span class="k"&gt;else
      &lt;/span&gt;&lt;span class="nv"&gt;JQ_PROCESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{hits, aggregations} | with_entries(select(.value != null ))"&lt;/span&gt;
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Processing &lt;/span&gt;&lt;span class="nv"&gt;$REQ&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  curl &lt;span class="nv"&gt;$URL&lt;/span&gt;/&lt;span class="nv"&gt;$INDEX&lt;/span&gt;/&lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt;?pretty &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; @&lt;span class="nv"&gt;$REQ&lt;/span&gt; | jq  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JQ_PROCESS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$RESPONSE&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Load into the tests
&lt;/h3&gt;

&lt;p&gt;Once the request and response pairs have been captured, they can be integrated into the unit tests using the &lt;a href="https://stackoverflow.com/questions/346372/whats-the-difference-between-faking-mocking-and-stubbing" rel="noopener noreferrer"&gt;test fake&lt;/a&gt; to stand in for Elasticsearch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5tepeybqgvr1w3s7l4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5tepeybqgvr1w3s7l4s.png" alt="unit test"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookstoreTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APITestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="bp"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@patch.object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Elasticsearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_authors_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_search&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;index_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

        &lt;span class="c1"&gt;# (1)
&lt;/span&gt;        &lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeElasticsearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;authors=King&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# (2)
&lt;/span&gt;        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# (5)
&lt;/span&gt;        &lt;span class="n"&gt;mock_search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;

        &lt;span class="c1"&gt;# (3)
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# (4)
&lt;/span&gt;        &lt;span class="n"&gt;mock_search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# (6)
&lt;/span&gt;        &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hits&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hits&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Detect when Elasticsearch changes
&lt;/h3&gt;

&lt;p&gt;The final piece of the puzzle comes from Martin Fowler, a &lt;a href="https://martinfowler.com/bliki/ContractTest.html" rel="noopener noreferrer"&gt;contract test&lt;/a&gt;.  The idea is to test the saved responses against a live instance to make sure that the assumed responses are still accurate.  If it fails, maybe it is time to regenerate the requests and fix your code!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncwj5vzt8anix4io41sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncwj5vzt8anix4io41sr.png" alt="contract test"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;elasticsearch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Elasticsearch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fake_es&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FakeElasticsearch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nose_parameterized&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parameterized&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestElasticsearchContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@unittest.skipUnless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LIVE_ELASTIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Elasticsearch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ES_HOST&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nd"&gt;@parameterized.expand&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;authors=King&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;orders=cancelled&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;isbn=99999&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_request_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# (1)
&lt;/span&gt;        &lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeElasticsearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# (2)
&lt;/span&gt;        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# (3)
&lt;/span&gt;        &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# (4)
&lt;/span&gt;        &lt;span class="c1"&gt;# &amp;lt;Elasticsearch returns the results of the query&amp;gt;
&lt;/span&gt;
        &lt;span class="c1"&gt;# (5)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertDictContainsSubset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;It should be noted that the above technique can be applied to any external API and not just Elasticsearch.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update March 31st, 2018&lt;/em&gt;: This blog post is also available as a &lt;a href="https://docs.google.com/presentation/d/1qLQnsRiA9C87U2q25_aw_ZwM4aEN1McR-flk-GCHdRI/edit?usp=sharing" rel="noopener noreferrer"&gt;Google Slides presentation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>unittest</category>
      <category>ci</category>
      <category>contracttest</category>
    </item>
    <item>
      <title>Deploy Atomically with Travis &amp; npm</title>
      <dc:creator>Jeff Farley</dc:creator>
      <pubDate>Tue, 31 Oct 2017 13:28:38 +0000</pubDate>
      <link>https://dev.to/jeffreymfarley/deploy-atomically-with-travis--npm-68b</link>
      <guid>https://dev.to/jeffreymfarley/deploy-atomically-with-travis--npm-68b</guid>
      <description>&lt;h1&gt;
  
  
  Deploy Atomically with Travis &amp;amp; npm
&lt;/h1&gt;

&lt;p&gt;I think I am a software developer because I am lazy.&lt;/p&gt;

&lt;p&gt;The second or third time I have to perform the same exact task, I find myself saying, “&lt;a href="https://xkcd.com/1319/"&gt;Ugh, can’t I tell the computer how to do it?&lt;/a&gt;”Â &lt;/p&gt;

&lt;p&gt;So imagine my reaction when our team’s deployment process started looking like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run build&lt;/code&gt; to create the minified packages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -am "Create Distribution" &amp;amp;&amp;amp; git push&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Navigate to GitHub&lt;/li&gt;
&lt;li&gt;Create a new release&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I was thinking steps 1–3 are easy enough to put in a shell script and steps 4–5 are probably scriptable, but is that all? What else needs to be done?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The version in &lt;code&gt;package.json&lt;/code&gt; was never getting updated and it would be nice to have that in synch with the GitHub release.&lt;/li&gt;
&lt;li&gt;Can this script be run after the CI build without having to task a human to manually run it?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub andÂ Releases
&lt;/h2&gt;

&lt;p&gt;My first step to full automation was digging into the details of GitHub releases. For starters, tags and releases are not the same thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Tagging"&gt;Tags are part of Git&lt;/a&gt;, the underlying version control system. They come in two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweightâ€Š–â€ŠA pointer to an existing commit&lt;/li&gt;
&lt;li&gt;Annotatedâ€Š–â€ŠA full object with name, timestamp and checksum hash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, they do not contain any files or changes to the code in the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Releases
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://help.github.com/articles/about-releases/"&gt;Releases are a GitHub extension of tags&lt;/a&gt;. They provide all the functionality of tags while also providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A zipped archive of the codebase at that point in time&lt;/li&gt;
&lt;li&gt;Any associated release binaries (likeÂ &lt;code&gt;.jar&lt;/code&gt; orÂ &lt;code&gt;.dll&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Does this fit our useÂ case?
&lt;/h3&gt;

&lt;p&gt;Not 100%. In our case, the minified files had to exist within the repository as part of a Django plugin in a larger architecture. We couldn’t really take advantage of GitHub releases since their files exist outside the code base.&lt;/p&gt;

&lt;p&gt;Also, there seems to be a chicken and egg problem with the &lt;code&gt;package.json&lt;/code&gt; version. I don’t know what the version number is until I tag, but I can’t store any code changes with the tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  npm version to theÂ rescue
&lt;/h2&gt;

&lt;p&gt;The clever folks at node already figured out this problem for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm version [major | minor | patch] -m "message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When running &lt;a href="https://docs.npmjs.com/cli/version"&gt;the command&lt;/a&gt;, npm will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the current git tag&lt;/li&gt;
&lt;li&gt;Bump the version in &lt;code&gt;package.json&lt;/code&gt; according to the type of bump (major, minor or patch)&lt;/li&gt;
&lt;li&gt;Run the &lt;code&gt;version&lt;/code&gt; script indicated inside of &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git add&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -m "message"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git tag &amp;lt;new tag number&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Perfect! And if we run &lt;code&gt;npm run build&lt;/code&gt; during the version script, the minified files will be packaged along with the updated &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  That’s great, how do I get Travis to doÂ that?
&lt;/h2&gt;

&lt;p&gt;We use Travis as our continuous integration server, but this step could be easily ported to CircleCI, AppVeyor or a number of other CI services.&lt;/p&gt;
&lt;h3&gt;
  
  
  The default use case is forÂ Releases
&lt;/h3&gt;

&lt;p&gt;If you start reading the Travis documentation, it is clear that their main use case is to build assets and push them to GitHub Releases. Since we already determined that isn’t for us, it was time to start experimenting and Googling.&lt;/p&gt;
&lt;h3&gt;
  
  
  NaÃ¯ve FirstÂ Steps
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;package.json&lt;/code&gt;, I wrote the version script to build the minified assets:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="p"&gt;...&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run build &amp;amp;&amp;amp; git add .&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postversion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git push &amp;amp;&amp;amp; git push --tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In &lt;code&gt;travis.yml&lt;/code&gt;, I had it call &lt;code&gt;npm version&lt;/code&gt; before deploying:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;before_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm version patch&lt;/span&gt;
&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;releases&lt;/span&gt;
  &lt;span class="na"&gt;skip_cleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And then ran it in Travis:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; foo@0.6.2 postversion /home/travis/build/foo/bar
&amp;gt; git push &amp;amp;&amp;amp; git push --tags
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use
    git push origin HEAD:&amp;lt;name-of-remote-branch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Detached HeadÂ Mode
&lt;/h3&gt;

&lt;p&gt;After a bit of Googling, it turns out that &lt;a href="https://github.com/travis-ci/travis-ci/issues/1701"&gt;Travis always runs in detached HEAD mode&lt;/a&gt;. It’s sort of a safety measure, but really puts a dent in how this auto deployment is supposed to work.&lt;/p&gt;

&lt;p&gt;I may be lazy, but I like a challenge.&lt;/p&gt;

&lt;p&gt;Basically, we have to have Travis perform the version steps at the tip of &lt;code&gt;master&lt;/code&gt; and then push it to our branch. Wait, how is it going to have the credentials to do that?&lt;/p&gt;
&lt;h3&gt;
  
  
  API Key
&lt;/h3&gt;

&lt;p&gt;Usually, Travis is performing read-only actions on the repository. When it has to write back to the repository, it needs to know which account to use. We could specify the credentials in the &lt;code&gt;travis.yml&lt;/code&gt; file, but then it would be exposed to everyone who views the file online. Doing this properly and safely takes the following (one time) setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new token at &lt;a href="https://github.com/settings/tokens"&gt;https://github.com/settings/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Copy the string of characters&lt;/li&gt;
&lt;li&gt;Open the settings in Travis&lt;/li&gt;
&lt;li&gt;Create a new environment variable &lt;code&gt;GITHUB_API_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Paste the characters in the value and make sure to uncheck “Display value in the build log”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ok, so the credentials can be exchanged, but we still haven’t fixed the detached head problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing a TravisÂ Script
&lt;/h2&gt;

&lt;p&gt;A few Google search results (&lt;a href="https://gist.github.com/willprice/e07efd73fb7f13f917ea"&gt;1&lt;/a&gt;, &lt;a href="https://github.com/JemsFramework/di/blob/trunk/.travis.yml"&gt;2&lt;/a&gt;) showed me that writing a shell script which gets executed by Travis was the solution to the detached head problem.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;.travis.yml&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;.travis/pu.sh&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This does everything we set out to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Builds the minified files&lt;/li&gt;
&lt;li&gt;Increments the version in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creates a new tag&lt;/li&gt;
&lt;li&gt;Pushes it back to the repository&lt;/li&gt;
&lt;li&gt;Which triggers another Travis build that…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;oh no!&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding the InfiniteÂ Loop
&lt;/h2&gt;

&lt;p&gt;Luckily, Travis has a way of saying “don’t run”. Change line 31 to say:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm version patch -m "chore: release version %s [skip ci]"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Whenever it encounters a commit message that contains &lt;code&gt;[skip ci]&lt;/code&gt;, Travis will not run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Wouldn’t it be nice if Travis could also update &lt;code&gt;CHANGELOG.MD&lt;/code&gt; too?&lt;/p&gt;

</description>
      <category>travis</category>
      <category>npm</category>
      <category>git</category>
    </item>
  </channel>
</rss>
