- couchdb 2017-03-19T16:58:15+01:00 https://fadeit.dk/blog/tag/couchdb.html Testing Express.js + CouchDB Applications with mock-couch 2015-07-05T22:30:00+02:00 https://fadeit.dk/blog /2015/07/05/testing-express-couchdb-apps-with-mock-couch <h2 id="prerequisites">Prerequisites</h2> <p>The code in this post is based upon <a href="http://coffeescript.org/">CoffeeScript</a>, <a href="http://mochajs.org/">Mocha</a> and <a href="http://gulpjs.com/">Gulp</a> but it should be easily adaptable to other stacks (e.g. <a href="https://www.javascript.com/">JavaScript</a>, <a href="https://jasmine.github.io/">Jasmine</a> and <a href="http://gruntjs.com/">Grunt</a>).</p> <h2 id="intro">Intro</h2> <p>Recently, I worked on a <a href="http://expressjs.com/">Express.js</a> based RESTful API with <a href="https://couchdb.apache.org/">CouchDB</a> storage. For various reasons, I mostly had end-to-end tests (testing HTTP endpoints) and very few unit tests. In the tests, I would rebuild the database before every <code class="highlighter-rouge">describe</code> block. This worked quite well but at one point I noticed that my feedback loops were beginning to get tediously slow (~30 seconds to run the full test suite). Thankfully, I discovered <a href="https://github.com/chris-l/mock-couch">mock-couch</a>: in-memory storage with CouchDB API. The switch was rather easy and, as a result, my test suite would finish in less than 10 seconds (even when reseting test data before each test).</p> <h2 id="setup">Setup</h2> <p>Our sample API will expose functionality to access a database of sofa’s. Let’s set it up.</p> <p>Structure and dependencies</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>mkdir -p sofa-api/specs <span class="nb">cd </span>sofa-api npm install mock-couch coffee-script gulp gulp-mocha should supertest express nano q </code></pre> </div> <p>gulpfile.coffee</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">require</span> <span class="s">'coffee-script/register'</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'gulp'</span> <span class="nx">mocha</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'gulp-mocha'</span> <span class="nx">sources</span> <span class="o">=</span> <span class="na">tests</span><span class="o">:</span> <span class="s">'./specs/**/*.spec.coffee'</span> <span class="c1"># Task to run tests with mock-couch backend</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'test'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'test'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3010</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5987</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas_test'</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">src</span><span class="p">(</span><span class="nx">sources</span><span class="p">.</span><span class="na">tests</span><span class="p">)</span> <span class="p">.</span><span class="na">pipe</span><span class="p">(</span><span class="nx">mocha</span><span class="p">())</span> <span class="c1"># Task to run integration tests (real CouchDB backend)</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'test-int'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'test-int'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3010</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5984</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas_test'</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">src</span><span class="p">(</span><span class="nx">sources</span><span class="p">.</span><span class="na">tests</span><span class="p">)</span> <span class="p">.</span><span class="na">pipe</span><span class="p">(</span><span class="nx">mocha</span><span class="p">())</span> <span class="c1"># Task to run our server</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'default'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'development'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3000</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5984</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas'</span> <span class="nx">server</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'./server'</span> <span class="nx">server</span><span class="p">.</span><span class="na">listen</span><span class="p">()</span> </code></pre> </div> <p>server.coffee</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'express'</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">()</span> <span class="nx">app</span><span class="p">.</span><span class="na">get</span> <span class="s">'/sofas'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="s">'not implemented'</span> <span class="nx">app</span><span class="p">.</span><span class="na">get</span> <span class="s">'/sofas/:sofa'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="s">'not implemented'</span> <span class="nx">exports</span><span class="p">.</span><span class="na">listen</span> <span class="o">=</span> <span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="nx">app</span><span class="p">.</span><span class="na">listen</span> <span class="nx">port</span><span class="p">,</span> <span class="nx">callback</span> </code></pre> </div> <p>Let’s create file <em>specs/sofas.spec.coffee</em> which is going to contain our tests.</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">should</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'should'</span> <span class="nx">describe</span> <span class="s">'Sofas API'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">describe</span> <span class="s">'/sofas'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">it</span> <span class="s">'should return all sofas with GET'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="c1"># TODO: write test</span> <span class="p">(</span><span class="no">false</span><span class="p">).</span><span class="na">should</span><span class="p">.</span><span class="na">be</span><span class="p">.</span><span class="na">true</span><span class="p">()</span> <span class="nx">describe</span> <span class="s">'/sofas/:sofa'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">it</span> <span class="s">'should return a particular sofa with GET'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="c1"># TODO: write test</span> <span class="p">(</span><span class="no">false</span><span class="p">).</span><span class="na">should</span><span class="p">.</span><span class="na">be</span><span class="p">.</span><span class="na">true</span><span class="p">()</span> </code></pre> </div> <p>If you run the tests right now, you should see similar output:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test </span>Sofas API /sofas 1<span class="o">)</span> should <span class="k">return </span>all sofas with GET /sofas/:sofa 2<span class="o">)</span> should <span class="k">return </span>a particular sofa with GET 0 passing <span class="o">(</span>30ms<span class="o">)</span> 2 failing 1<span class="o">)</span> Sofas API /sofas should <span class="k">return </span>all sofas with GET: AssertionError: expected <span class="nb">false </span>to be <span class="nb">true </span>at Context.&lt;anonymous&gt; <span class="o">(</span>specs/sofas.spec.coffee:8:7<span class="o">)</span> 2<span class="o">)</span> Sofas API /sofas/:sofa should <span class="k">return </span>a particular sofa with GET: AssertionError: expected <span class="nb">false </span>to be <span class="nb">true </span>at Context.&lt;anonymous&gt; <span class="o">(</span>specs/sofas.spec.coffee:13:7<span class="o">)</span> Message: 2 tests failed. </code></pre> </div> <h2 id="implementation">Implementation</h2> <p>Sofas will be stored in CouchDB with the following structure:</p> <div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nt">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CouchDB assigned id"</span><span class="p">,</span><span class="w"> </span><span class="nt">"_rev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CouchDB assigned revision"</span><span class="p">,</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Name of the sofa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"price"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Price of the sofa"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>With this in mind, let’s build a module for accessing the database.</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">nano</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'nano'</span> <span class="nx">Q</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'q'</span> <span class="k">class</span> <span class="nx">DAL</span> <span class="na">constructor</span><span class="o">:</span> <span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="vi">@</span><span class="na">database</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="vi">@</span><span class="na">conn</span> <span class="o">=</span> <span class="nx">nano</span><span class="p">(</span><span class="s">"http://localhost:</span><span class="si">#{</span><span class="nx">port</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="c1"># Returns a promise reseting database</span> <span class="na">resetDatabase</span><span class="o">:</span> <span class="p">(</span><span class="nx">withSamples</span><span class="o">=</span><span class="no">false</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">docs</span> <span class="o">=</span> <span class="p">[</span><span class="vi">@</span><span class="na">_sofaViews</span><span class="p">]</span> <span class="k">if</span> <span class="nx">withSamples</span> <span class="nx">docs</span> <span class="o">=</span> <span class="nx">docs</span><span class="p">.</span><span class="na">concat</span> <span class="vi">@</span><span class="na">_sofaSamples</span> <span class="vi">@</span><span class="na">_deleteDb</span><span class="p">()</span> <span class="p">.</span><span class="na">then</span> <span class="o">=&gt;</span> <span class="vi">@</span><span class="na">_createDb</span><span class="p">()</span> <span class="p">.</span><span class="na">then</span> <span class="o">=&gt;</span> <span class="nx">Q</span><span class="p">.</span><span class="na">all</span><span class="p">(</span><span class="nx">docs</span><span class="p">.</span><span class="na">map</span> <span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="vi">@</span><span class="na">_insertDoc</span><span class="p">(</span><span class="nx">d</span><span class="p">))</span> <span class="c1"># Returns a promise of sofas</span> <span class="c1"># If you provide the id- you'll get an array with a particular sofa in it</span> <span class="na">getSofas</span><span class="o">:</span> <span class="p">(</span><span class="nx">id</span><span class="o">=</span><span class="no">null</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{}</span> <span class="nx">params</span><span class="p">.</span><span class="na">key</span> <span class="o">=</span> <span class="nx">id</span> <span class="k">if</span> <span class="nx">id</span><span class="o">?</span> <span class="vi">@</span><span class="na">_view</span><span class="p">(</span><span class="s">'sofas'</span><span class="p">,</span> <span class="s">'all'</span><span class="p">,</span> <span class="nx">params</span><span class="p">)</span> <span class="p">.</span><span class="na">then</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">res</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="nx">sofas</span> <span class="o">=</span> <span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="na">value</span> <span class="k">for</span> <span class="nx">row</span> <span class="k">in</span> <span class="nx">body</span><span class="p">.</span><span class="na">rows</span><span class="p">)</span> <span class="k">return</span> <span class="nx">sofas</span> <span class="na">_sofaViews</span><span class="o">:</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'_design/sofas'</span> <span class="na">views</span><span class="o">:</span> <span class="na">all</span><span class="o">:</span> <span class="na">map</span><span class="o">:</span> <span class="p">(</span><span class="nx">doc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">if</span> <span class="nx">doc</span><span class="p">.</span><span class="na">type</span> <span class="o">is</span> <span class="s">'Sofa'</span> <span class="nx">emit</span> <span class="nx">doc</span><span class="p">.</span><span class="na">_id</span><span class="p">,</span> <span class="nx">doc</span> <span class="na">_sofaSamples</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa1'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa1'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">400</span> <span class="p">}</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa2'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa2'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">300</span> <span class="p">}</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa3'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa3'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">500</span> <span class="p">}</span> <span class="p">]</span> <span class="c1"># Returns promise of creating database</span> <span class="na">_createDb</span><span class="o">:</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">db</span><span class="p">.</span><span class="na">create</span> <span class="vi">@</span><span class="na">database</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of deleting database</span> <span class="na">_deleteDb</span><span class="o">:</span> <span class="p">(</span><span class="nx">ignoreMissing</span><span class="o">=</span><span class="no">true</span><span class="p">)</span><span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">db</span><span class="p">.</span><span class="na">destroy</span> <span class="vi">@</span><span class="na">database</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">err</span> <span class="o">=</span> <span class="no">null</span> <span class="k">if</span> <span class="nx">err</span><span class="o">?</span> <span class="o">and</span> <span class="nx">err</span><span class="p">.</span><span class="na">statusCode</span> <span class="o">is</span> <span class="mi">404</span> <span class="o">and</span> <span class="nx">ignoreMissing</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of inserting a doc</span> <span class="na">_insertDoc</span><span class="o">:</span> <span class="p">(</span><span class="nx">doc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">use</span><span class="p">(</span><span class="vi">@</span><span class="na">database</span><span class="p">).</span><span class="na">insert</span> <span class="nx">doc</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of CouchDB view</span> <span class="na">_view</span><span class="o">:</span> <span class="p">(</span><span class="nx">designDoc</span><span class="p">,</span> <span class="nx">view</span><span class="p">,</span> <span class="nx">params</span><span class="o">=</span><span class="p">{})</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">use</span><span class="p">(</span><span class="vi">@</span><span class="na">database</span><span class="p">).</span><span class="na">view</span> <span class="nx">designDoc</span><span class="p">,</span> <span class="nx">view</span><span class="p">,</span> <span class="nx">params</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="nx">module</span><span class="p">.</span><span class="na">exports</span> <span class="o">=</span> <span class="nx">DAL</span> </code></pre> </div> <p>What’s missing is the endpoint and test implementation. Let’s start with the tests.</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/specs/sofas.spec.coffee b/specs/sofas.spec.coffee index 8c1eb84..efbff58 100644 </span><span class="gd">--- a/specs/sofas.spec.coffee </span><span class="gi">+++ b/specs/sofas.spec.coffee </span><span class="gu">@@ -1,13 +1,54 @@ </span><span class="gd">-should = require 'should' </span><span class="gi">+should = require 'should' +DAL = require '../dal' +request = require 'supertest' +request = request "http://localhost:#{process.env.PORT}" +server = require '../server' + +app = null </span> describe 'Sofas API', -&gt; <span class="gi">+ beforeEach (done) -&gt; + # Reset database and start express server before each test + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + dal.resetDatabase(true) + .then -&gt; + app = server.listen done + .fail (err) -&gt; done err + + # Close express server after each test + afterEach (done) -&gt; + app.close done + + </span> describe '/sofas', -&gt; <span class="gd">- it 'should return all sofas with GET', -&gt; - # TODO: write test - (false).should.be.true() </span><span class="gi">+ it 'should return all sofas with GET', (done) -&gt; + request + .get '/sofas' + .expect 200 + .expect (res) -&gt; + sofas = res.body + sofas.should.have.length(3) + for s in sofas + s.should.have.property('_id') + s.should.have.property('_rev') + s.should.have.property('name') + s.should.have.property('price') + + .end done + + </span> describe '/sofas/:sofa', -&gt; <span class="gd">- it 'should return a particular sofa with GET', -&gt; - # TODO: write test - (false).should.be.true() </span><span class="gi">+ it 'should return a particular sofa with GET', (done) -&gt; + request + .get '/sofas/sofa1' + .expect 200 + .expect (res) -&gt; + sofa = res.body + sofa.should.have.property('_id') + sofa.should.have.property('_rev') + sofa.should.have.property('name') + sofa.should.have.property('price') + + .end done </span></code></pre> </div> <p>Now the endpoints:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/server.coffee b/server.coffee index 98f1918..a0d30ce 100644 </span><span class="gd">--- a/server.coffee </span><span class="gi">+++ b/server.coffee </span><span class="gu">@@ -1,16 +1,27 @@ </span> express = require 'express' <span class="gi">+DAL = require './dal' </span> <span class="gi">+dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) </span> app = express() app.get '/sofas', (req, res) -&gt; <span class="gd">- throw new Error 'not implemented' </span><span class="gi">+ dal.getSofas() + .then (sofas) -&gt; + res.json(sofas) + .fail (err) -&gt; res.status(err.statusCode).send() </span> app.get '/sofas/:sofa', (req, res) -&gt; <span class="gd">- throw new Error 'not implemented' </span><span class="gi">+ dal.getSofas(req.params.sofa) + .then (sofas) -&gt; + if sofas.length isnt 1 + res.status(404).send() + else + res.json(sofas[0]) + .fail (err) -&gt; res.status(err.statusCode).send() </span> exports.listen = (callback) -&gt; </code></pre> </div> <p>At this point we can execute <em>gulp test-int</em> command to run tests on real CouchDB:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test</span>-int Sofas API /sofas ✓ should <span class="k">return </span>all sofas with GET /sofas/:sofa ✓ should <span class="k">return </span>a particular sofa with GET 2 passing <span class="o">(</span>204ms<span class="o">)</span> </code></pre> </div> <h2 id="mock-couch-tests">mock-couch tests</h2> <p>So how do we use mock-couch for this? Mock-couch is an HTTP server based on <a href="http://mcavage.me/node-restify/">Restify</a>. Note that <a href="https://nodejs.org/">Node.js</a> runtime is single-threaded therefore we won’t be able to run both express and mock-couch at the same time. However, it is possible to run mock-couch on another process.</p> <p>Node provides a couple of ways to create processes out of the box. We’ll use one called <a href="https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options">forking</a>: not only we’ll be able to control the lifecycle of the forked child process but we’ll also have the ability to send and receive messages between parent and the child.</p> <p>OK, less talk and more forking. We’ll do the forking in <em>gulp test</em> task:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/gulpfile.coffee b/gulpfile.coffee index cbd61b5..1200834 100644 </span><span class="gd">--- a/gulpfile.coffee </span><span class="gi">+++ b/gulpfile.coffee </span><span class="gu">@@ -6,14 +6,48 @@ mocha = require 'gulp-mocha' </span> sources = tests: './specs/**/*.spec.coffee' <span class="gi">+ </span> # Task to run tests with mock-couch backend gulp.task 'test', -&gt; process.env.NODE_ENV = 'test' process.env.PORT = 3010 process.env.COUCH_PORT = 5987 process.env.DATABASE = 'sofas_test' <span class="gd">- gulp.src(sources.tests) - .pipe(mocha()) </span><span class="gi">+ + fork = require('child_process').fork + couchProcess = fork('node_modules/gulp/bin/gulp.js', ['mock-couch'], {cwd: __dirname}) + couchProcess.on 'message', (msg) =&gt; + if msg is 'listening' + gulp.src(sources.tests) + .pipe(mocha()) + .once('error', (err) -&gt; + process.exit(1) + ) + .once('end', -&gt; + process.exit() + ) + + process.on 'exit', -&gt; + couchProcess.kill() + +gulp.task 'mock-couch', -&gt; + process.env.NODE_ENV = 'test' + process.env.PORT = 3010 + process.env.COUCH_PORT = 5987 + process.env.DATABASE = 'sofas_test' + + mockCouch = require 'mock-couch' + couchdb = mockCouch.createServer() + DAL = require './dal' + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + docs = [dal._sofaViews] + docs = docs.concat dal._sofaSamples + + couchdb.addDB process.env.DATABASE, docs + + couchdb.listen process.env.COUCH_PORT, -&gt; + process.send 'listening' + </span> # Task to run integration tests (real CouchDB backend) gulp.task 'test-int', -&gt; </code></pre> </div> <p>Also, we need a slight change to the test setup:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/specs/sofas.spec.coffee b/specs/sofas.spec.coffee index efbff58..93e7bd2 100644 </span><span class="gd">--- a/specs/sofas.spec.coffee </span><span class="gi">+++ b/specs/sofas.spec.coffee </span><span class="gu">@@ -9,12 +9,17 @@ app = null </span> describe 'Sofas API', -&gt; beforeEach (done) -&gt; <span class="gd">- # Reset database and start express server before each test - dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) - dal.resetDatabase(true) - .then -&gt; - app = server.listen done - .fail (err) -&gt; done err </span><span class="gi">+ + if process.env.NODE_ENV is 'test-int' + # Reset database and start express server before each test + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + dal.resetDatabase(true) + .then -&gt; + app = server.listen done + .fail (err) -&gt; done err + else + app = server.listen done + </span> # Close express server after each test afterEach (done) -&gt; </code></pre> </div> <h2 id="moment-of-truth">Moment of Truth</h2> <p>If you try running <code class="highlighter-rouge">gulp test</code> right now, you should see similar output:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test </span>Sofas API /sofas ✓ should <span class="k">return </span>all sofas with GET <span class="o">(</span>125ms<span class="o">)</span> /sofas/:sofa ✓ should <span class="k">return </span>a particular sofa with GET <span class="o">(</span>41ms<span class="o">)</span> 2 passing <span class="o">(</span>178ms<span class="o">)</span> </code></pre> </div> <p>As you can see the tests completed slightly faster than with real CouchDB. In a small code base like this, it’s hardly worth switching to mock-couch, if execution speed is your only motivation. However, with bigger codebases, the speed improvement can become very apparent.</p> <p>Besides speed, there’s another benefit to this approach: you don’t actually need CouchDB to test your project. The simplified build process is especially useful with Continuous Integration platforms like <a href="http://travis-ci.org/">Travis CI</a> and <a href="https://codeship.com">Codeship</a>.</p> <h2 id="resources">Resources</h2> <ul> <li><a href="https://github.com/reederz/express-mock-couch-showcase">Code on GitHub</a></li> <li><a href="https://github.com/chris-l/mock-couch">mock-couch</a></li> </ul>