<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Henrique Barcelos's Blog - Software Engineering, Crypto &amp; Blockchain and much more]]></title><description><![CDATA[Brazilian, Software Engineer / Architect, Crypto & Blockchain Enthusiast, Libertarian, Metal Head, Krav Maga Practitioner, Beer Lover, Amateur Photographer, (ba]]></description><link>https://blog.hbarcelos.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 06 Sep 2026 05:06:29 GMT</lastBuildDate><atom:link href="https://blog.hbarcelos.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Flexbox Gotchas Reloaded]]></title><description><![CDATA[Important: this article describes a problem that cannot be reproduced with Google Chrome, only with Firefox and Edge (I did not test with other browsers). Spoiler alert: it seems like Chrome intentionally deviates from the spec standard.
Intro
Despit...]]></description><link>https://blog.hbarcelos.dev/flexbox-gotchas-reloaded</link><guid isPermaLink="true">https://blog.hbarcelos.dev/flexbox-gotchas-reloaded</guid><category><![CDATA[CSS3]]></category><category><![CDATA[css flexbox]]></category><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Mon, 20 Apr 2020 12:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1587312963843/_ITnR6Yk7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Important:</strong> this article describes a problem that cannot be reproduced with Google Chrome, only with Firefox and Edge (I did not test with other browsers). Spoiler alert: <a target='_blank' rel='noopener noreferrer'  href="https://stackoverflow.com/a/49579315/1798341">it seems like Chrome intentionally deviates from the spec standard</a>.</p>
<h2 id="intro">Intro</h2>
<p>Despite of all the jokes saying it is not even a real programming language, more that 20 years since its creation, CSS is still one of the most misunderstood web technologies.</p>
<p>While it allows us to describe complex layouts in a high-level fashion, sometimes we bump into issues with  corner cases. Also the CSS spec is not getting any smaller with time, and while I certainly do not miss juggling around with <code>float</code> and <code>clear</code>, it is hard to deny that flexbox and grid brought a lot of complexity to CSS.</p>
<h2 id="tl-dr">TL;DR</h2>
<p>I stumbled upon an issue with flexbox and <code>white-space: nowrap</code>, causing a flex child width to be larger than its parents. Visit <a target='_blank' rel='noopener noreferrer'  href="https://codesandbox.io/s/flexbox-vs-white-space-nowrap-causes-child-to-overflow-parent-3x2cl?file=/src/styles.css">this codesandbox</a> to view the solution.</p>
<h2 id="flexbox-all-the-things">Flexbox all the things</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587314725293/SUhEj6NRV.png" alt="all_the_things_meme_guy_by_seiyalove-d4m2e4l.png"></p>
<p>I must admit that I might be overusing flexbox, but the reality is that other methods for centering content both vertically and horizontally are way too hacky.</p>
<p>I had this screen from the app I am currently working on whose outer-most wrapper was a flex container. The screen is supposed to be responsive, so there was no limits for its width. Deeply nested into the HTML tree there is another flex container whose children contained text that could potentially spread across multiple lines.</p>
<p>I was able to create a minimum reproducible example, so please forgive me if it looks a bit contrived.</p>
<p>Here is the markup:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"outer-flex-container"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"outer-flex-child"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"inner-flex-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"inner-flex-child"</span>&gt;</span>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec
        blandit massa, sed fringilla odio. Quisque nec ipsum molestie,
        maximus.
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>And here are the styles:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.outer-flex-container</span> {
  <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> dashed red;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">16px</span>;
}

<span class="hljs-selector-class">.outer-flex-child</span> {
  <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> solid orangered;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">16px</span>;
}

<span class="hljs-selector-class">.inner-flex-container</span> {
  <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> dashed teal;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: center;
}

<span class="hljs-selector-class">.inner-flex-child</span> {
  <span class="hljs-attribute">background</span>: paleturquoise;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">16px</span>;
}
</code></pre>
<p>The result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587322413956/EHB7OrxNM.png" alt="01-with-line-wrapping.png"></p>
<h2 id="the-issue">The issue</h2>
<p>Because of layout constraints, I need the text in <code>.inner-flex-child</code> to be displayed in a single line, so I happily went along with:</p>
<pre><code class="lang-diff"> .inner-flex-child {
   background: paleturquoise;
   padding: 16px;
<span class="hljs-addition">+  overflow: hidden;</span>
<span class="hljs-addition">+  text-overflow: ellipsis;</span>
<span class="hljs-addition">+  white-space: nowrap;</span>
 }
</code></pre>
<p>The result however surprised me:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587323795898/Z6dTCFCI1.png" alt="02-nowrap-breaks-layout.png"></p>
<p>Oddly the <code>.inner-flex-child</code> now stopped respecting its parent&#39;s width. Furthermore, it seems like it is forcing both <code>.inner-flex-container</code> and <code>.outer-flex.child</code> to overflow <code>.outer-flex-container</code> width.</p>
<blockquote>
<p>Okay, let&#39;s just:</p>
<pre><code class="lang-diff"> .inner-flex-child {
   background: paleturquoise;
   padding: 16px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
<span class="hljs-addition">+  width: 100%;</span>
 }
</code></pre>
<p>And that will do it!</p>
</blockquote>
<p>Nop! That did not work at all. Results were still the same. I tried then every possible combination of <code>width</code>, <code>max-width</code>, <code>min-width</code>, <code>flex-basis</code> I could think of without success.</p>
<p>For example, if I set a fixed <code>width</code> for <code>.inner-flex-child</code>:</p>
<pre><code class="lang-diff"> .inner-flex-child {
   background: paleturquoise;
   padding: 16px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
<span class="hljs-addition">+  width: 500px;</span>
 }
</code></pre>
<p>I would get:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587324951538/Z8MTHAoeo.png" alt="03-inner-child-fixed-width.png"></p>
<p>Works as expected if the viewport is larger than <code>500px</code>, but expectedly if I had a smaller screen, the problem would be back:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587325120512/lEptzLkOK.png" alt="04-inner-child-fixed-width-smaller-viewport.png"></p>
<p>Switching <code>width</code> to <code>max-width</code> however did not do the trick.</p>
<p>Another attempt had me setting <code>flex-basis</code>, <code>flex-grow</code> and <code>flex-shrink</code>:</p>
<pre><code class="lang-diff"> .inner-flex-child {
   background: paleturquoise;
   padding: 16px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
<span class="hljs-addition">+  flex: 200px 0 1;</span>
 }
</code></pre>
<p>The idea here was to set the <code>flex-basis</code> to a fixed width, do not allow <code>.inner-flex-child</code> to grow, but do allow it to shrink. However, the result was even weirder:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587333563435/y4Pxi6rjq.png" alt="04.1-flex-basis-does-not-fix-it.png"></p>
<p>Even though the content size was respecting the <code>200px</code> I set, <code>.inner-flex-container</code> was still overflowing its parents. My head was now a complete mess.</p>
<h1 id="halt-duckduckgo-time">Halt! DuckDuckGo time</h1>
<p>Defeated, I resorted to the knowledge of the world wide web. I started digging with DuckDuckGo (<a target='_blank' rel='noopener noreferrer'  href="https://duckduckgo.com/privacy">just like Google, but it respects your privacy</a>) and found this  <a target='_blank' rel='noopener noreferrer'  href="https://css-flexbox-text-ellipsis.dinhquangtrung.net/">example</a>. Apparently setting <code>min-width: 0;</code> in <code>.inner-flex-child</code> was supposed to do the trick:</p>
<pre><code class="lang-diff"> .inner-flex-child {
   background: paleturquoise;
   padding: 16px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
<span class="hljs-addition">+  min-width: 0;</span>
 }
</code></pre>
<p>But it did not! I was bummed out at first, but I kept on digging. Of course there was something regarding that <a target='_blank' rel='noopener noreferrer'  href="https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size">in StackOverflow</a>. However the solution pointed to the same <code>min-width: 0</code> trick. I was almost dismissing it when I saw it had a link to the  <a target='_blank' rel='noopener noreferrer'  href="https://www.w3.org/TR/css-flexbox-1/#min-size-auto">spec</a>.</p>
<p>The linked spec session starts with:</p>
<blockquote>
<p>Note: The <code>auto</code> keyword, representing an automatic minimum size, is the new initial value of the <code>min-width</code> and <code>min-height</code> properties. The keyword was previously defined in this specification, but is now defined in the CSS Sizing module.</p>
</blockquote>
<p>From that I understood that flex children have the value of <code>min-width</code> and <code>min-height</code> default to <code>auto</code> if not specified.</p>
<p>Then it goes on:</p>
<blockquote>
<p>To provide a more reasonable default minimum size for flex items, the used value of a main axis automatic minimum size on a flex item that is not a scroll container is a content-based minimum size; for scroll containers the automatic minimum size is zero, as usual.</p>
</blockquote>
<p>That&#39;s a mouthful. I was in rush/lazy mode by the time I was reading this, so I gave up. This is a behavioral pattern I fallback into when I am tired and with enough training I learnt how to recognize this and went out &mdash; of the room, since I am writing this in the middle of the Covid-19 pandemic, I could not leave the apartment &mdash; for a while.</p>
<p>After a cup of coffee and trying to put myself in the right mental state, I went back to the same <a target='_blank' rel='noopener noreferrer'  href="https://stackoverflow.com/a/36247448/1798341">SO question</a>. It actually does a good job &quot;translating&quot; the spec into something more digestible:</p>
<blockquote>
<p>With regard to the auto value...</p>
<blockquote>
<p>On a flex item whose overflow is visible in the main axis, when specified on the flex item’s main-axis min-size property, specifies an automatic minimum size. It otherwise computes to 0.</p>
</blockquote>
<p>In other words:</p>
<ul>
<li>The min-width: auto and min-height: auto defaults apply only when overflow is visible.</li>
<li>If the overflow value is not visible, the value of the min-size property is 0.</li>
<li>Hence, overflow: hidden can be a substitute for min-width: 0 and min-height: 0.</li>
</ul>
</blockquote>
<p>But here is the problem: <code>.inner-flex-child</code> is already a flex container, because it has <code>overflow: hidden</code> set. Damn you, CSS!</p>
<p>Reading on, I reached this section:</p>
<blockquote>
<h3 id="you-ve-applied-min-width-0-and-the-item-still-doesn-t-shrink-">You&#39;ve applied min-width: 0 and the item still doesn&#39;t shrink?</h3>
<h4 id="nested-flex-containers">Nested Flex Containers</h4>
<p>If you&#39;re dealing with flex items on multiple levels of the HTML structure, <strong>it may be necessary to override the default min-width: auto / min-height: auto on items at higher levels.</strong></p>
<p>Basically, a higher level flex item with min-width: auto can prevent shrinking on items nested below with min-width: 0.</p>
</blockquote>
<p>That was exactly the problem I was facing. I was only meddling with <code>.inner-flex-child</code> and <code>.inner-flex-container</code> the whole time. However, I had more flex content above in the DOM hierarchy. Dumbass!</p>
<p>So I tried the following:</p>
<pre><code class="lang-diff"> .outer-flex-child {
   border: 2px solid orangered;
   padding: 16px;
<span class="hljs-addition">+  min-width: 0;</span>
 }
</code></pre>
<p>Aaaaaand bingo!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1587331472615/5pInm-4eD.png" alt="05-fixed-issue-in-outer-flex-child.png"></p>
<p>I decided to play around with the working example and found out that the following approaches have the same effect:</p>
<pre><code class="lang-diff"> .outer-flex-child {
   border: 2px solid orangered;
   padding: 16px;
<span class="hljs-addition">+ overflow: hidden;</span>
 }
</code></pre>
<p>According to the spec, this fixes the issue because <code>.outer-flex-child</code> is now a scroll container, so <code>min-width</code> default value falls-back to <code>0</code>. The downside of this is that if it contained any children that might be out of bounds &mdash; such as a tooltip or a modal with <code>position: absolute</code> &mdash; they would be clipped out.</p>
<pre><code class="lang-diff"> .outer-flex-child {
   border: 2px solid orangered;
   padding: 16px;
<span class="hljs-addition">+  max-width: 100%;</span>
 }
</code></pre>
<p>While not directly mentioned in the linked spec section, I noticed that <code>.outer-flex-container</code> width itself was not affected by the overflowing content. When using <code>%</code> as unit, it will be always relative to the parent&#39;s dimensions, so setting <code>max-width: 100%</code> does the trick as well, at least in this specific case.</p>
<p><a target='_blank' rel='noopener noreferrer'  href="https://codesandbox.io/s/flexbox-vs-white-space-nowrap-causes-child-to-overflow-parent-3x2cl?file=/src/styles.css">Here is a codesandbox</a>  with the example I used throughout this article and the possible solutions.</p>
<p>That&#39;s all folks!</p>
]]></content:encoded></item><item><title><![CDATA[TDD made simple with Mocha and Chai]]></title><description><![CDATA[Intro
From the dark ol’ days of writing an entire application and only then starting to test it (often, manually) till nowadays, I have scoured a painful path of unending bug-fixing in production through the nights, many times not even knowing what w...]]></description><link>https://blog.hbarcelos.dev/tdd-made-simple-with-mocha-and-chai</link><guid isPermaLink="true">https://blog.hbarcelos.dev/tdd-made-simple-with-mocha-and-chai</guid><category><![CDATA[TDD (Test-driven development)]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[mocha]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Wed, 05 Feb 2020 20:52:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1580936502981/F0G8Tjy79.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>From the dark ol’ days of writing an entire application and only then starting to test it (often, manually) till nowadays, I have scoured a painful path of unending bug-fixing in production through the nights, many times not even knowing what was causing those bugs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580924443476/utCTzwpjk.jpeg" alt="disappearing-bugs.jpeg"></p>
<blockquote>
<p>Yeah, something crazy is going on!</p>
</blockquote>
<p>Since I first heard of Test Driven Development, it changed the way I think about software development.</p>
<p>I will not be digressing about TDD philosophy and its implications here, because  <a target='_blank' rel='noopener noreferrer'  href="http://www.agiledata.org/essays/tdd.html">a lot of</a>  <a target='_blank' rel='noopener noreferrer'  href="https://www.martinfowler.com/bliki/TestDrivenDevelopment.html">more qualified people</a>  <a target='_blank' rel='noopener noreferrer'  href="https://medium.com/javascript-scene/tdd-changed-my-life-5af0ce099f80">have done it before me</a>. So let’s get to the code!</p>
<hr>
<h2 id="first-the-problem-and-its-solution">First, the problem and its solution</h2>
<p>A long time ago in a galaxy far far away, I ended up in a problem: I had to monitor a “stream” (more like a polling) of events that were being created at a certain application in my Node.JS backend. This “stream” was not uniform and, most of the time, no event occurred.</p>
<p>I could not use websockets, so I would have to buffer these events in my backend. I thought using a database (even an in-memory one like Redis) just for that was too much. Then I decided that I would keep the events in memory and as my application did not care for all events that ever happened, I would keep only the last N of them.</p>
<p>Since Node.JS arrays are dynamic, they did not fit my needs. I did not want a fixed-size array implementation, what I needed was a fixed-sized first-in/first-out (FIFO) data structure, AKA a <strong>queue</strong>, which instead of overflowing when full, should pop its first element and then add the new one at the end.</p>
<h2 id="expected-behavior">Expected behavior</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580925435400/pKSQdIeel.png" alt="keep-calm-behave-yourself.png"></p>
<blockquote>
<p>You better do that!</p>
</blockquote>
<p>The data structure described above is rather simple. Its expected behavior could be summarized as follows:</p>
<p>Adding elements:</p>
<ul>
<li>When it is not full, it should add the new element to the end; its size should be increased by 1.</li>
<li>When it is full, it should remove the first element and then add the new element to the end; its size must not change.<ul>
<li>The removed element should be returned.</li>
</ul>
</li>
</ul>
<p>Removing elements:</p>
<ul>
<li>When it is not empty, it should remove the first element and return it; its size should be decreased by 1.</li>
<li>When it is empty, it should throw an error.</li>
</ul>
<h2 id="a-mocha-to-go-please-">A Mocha to go, please!</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580925569725/ChNWZ_mGT.jpeg" alt="mocha-coffee.jpeg"></p>
<blockquote>
<p>Looks delicious! ;9</p>
</blockquote>
<p>From the docs:</p>
<blockquote>
<p> <a target='_blank' rel='noopener noreferrer'  href="https://mochajs.org/">Mocha</a>  is a feature-rich JavaScript test framework running on  <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/">Node.js</a>  and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on  <a target='_blank' rel='noopener noreferrer'  href="https://github.com/mochajs/mocha">GitHub</a>.</p>
</blockquote>
<h3 id="installation">Installation</h3>
<pre><code class="lang-bash">yarn add --dev mocha
<span class="hljs-comment"># or with NPM:</span>
<span class="hljs-comment"># npm install --save-dev mocha</span>
</code></pre>
<h3 id="writing-tests">Writing tests</h3>
<p>To create a test suite, you use a globally defined function called <code>describe</code>. To add test cases to a suite, you should use another global function <code>it</code>:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="d87cd45d24e3a0986ca7f0eb3221879c"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/d87cd45d24e3a0986ca7f0eb3221879c" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/d87cd45d24e3a0986ca7f0eb3221879c</a></div><p>Suites can be nested indefinitely when you want to group your test cases. Mocha will collect all your suites recursively and execute all test cases it find within them in the order they are declared.</p>
<p>And that’s probably about all you need to tedknow about Mocha to get star (at least for basic usage). It excels so much for simplicity and extensibility, that it allows you to use whatever assertion library and other plugins you want.</p>
<h3 id="running-tests">Running tests</h3>
<pre><code class="lang-bash">yarn mocha <span class="hljs-string">'&lt;path-to-test-file&gt;'</span>
<span class="hljs-comment"># or with NPM's npx:</span>
<span class="hljs-comment"># npx mocha '&lt;path-to-test-file&gt;'</span>
</code></pre>
<h2 id="enter-chai">Enter Chai</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580926329312/6Ji9lcmw2.jpeg" alt="chai.jpeg"></p>
<blockquote>
<p>I used to think that Node.JS developers only like coffee… Guess I was not totally right (:</p>
</blockquote>
<p>By default, Mocha can be used along with Node.js native <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/dist/latest-v12.x/docs/api/assert.html"><code>assert</code></a> module. It works just fine, however I don&#39;t find its developer experience to be exactly great. For that reason, we will use a 3rd-party assertion library called Chai.</p>
<p>From the docs:</p>
<blockquote>
<p> <a target='_blank' rel='noopener noreferrer'  href="https://www.chaijs.com/">Chai</a>  is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any JavaScript testing framework.</p>
</blockquote>
<h3 id="installation">Installation</h3>
<pre><code class="lang-bash">yarn add --dev chai
<span class="hljs-comment"># or with NPM:</span>
<span class="hljs-comment"># npm install --save-dev chai</span>
</code></pre>
<h3 id="usage">Usage</h3>
<p>Chai offers 3 different styles for writing assertions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580926746587/16qkOEPbr.png" alt="chai-assertion-styles.png"></p>
<blockquote>
<p>Chai allows you to pick your own poison.</p>
</blockquote>
<p>All of them have the same capabilities, so choosing one or another is more a matter of preference than of objective facts. I like to use the <code>expect</code> interface.</p>
<h2 id="oh-tests-oh-dreaded-tests-">Oh, tests! Oh, dreaded tests!</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580926931683/xm8mX-yOR.png" alt="bug.png"></p>
<blockquote>
<p>These bug’s bites hurt a lot; better kill’em before they kill you!</p>
</blockquote>
<p>Going back to our original problem, let’s translate the expected behavior into mocha test suites. But first, let’s do some setup:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> chai = <span class="hljs-built_in">require</span>(<span class="hljs-string">"chai"</span>);
<span class="hljs-keyword">const</span> expect = chai.expect;

<span class="hljs-keyword">const</span> RoundQueue = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./round-linked-queue"</span>);

describe(<span class="hljs-string">"Round-Queue"</span>, () =&gt; {
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/initial/round-linked-queue.test.js">Source</a></p>
</blockquote>
<h3 id="testing-queue-creation">Testing queue creation</h3>
<p>The main reason why we are creating this data structure is that it has to be a limited size, so let&#39;s make sure it has such property:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> chai = <span class="hljs-built_in">require</span>(<span class="hljs-string">"chai"</span>);
<span class="hljs-keyword">const</span> expect = chai.expect;

<span class="hljs-keyword">const</span> RoundQueue = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./round-linked-queue"</span>);

describe(<span class="hljs-string">"Round-Queue"</span>, () =&gt; {
  describe(<span class="hljs-string">"When creating an instance"</span>, () =&gt; {
    it(<span class="hljs-string">"Should properly set the maxLength property"</span>, () =&gt; {
      <span class="hljs-keyword">const</span> queueLength = <span class="hljs-number">3</span>;

      <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(queueLength);

      expect(queue.maxLength).to.equal(queueLength);
    });
  });
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/1st-test-case/round-linked-queue.test.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/1st-test-case#diff-873978c023e14c5220c2c83000ee2c7b">diff</a></p>
</blockquote>
<p>Next we implement just enough code to make the test above pass:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-keyword">constructor</span>(maxLength) {
    <span class="hljs-keyword">this</span>._maxLength = maxLength;
  }

  get maxLength() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._maxLength;
  }
}

<span class="hljs-built_in">module</span>.exports = RoundLinkedQueue;
</code></pre>
<blockquote>
<p> <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/1st-test-case/round-linked-queue.js">Source</a></p>
</blockquote>
<p>To run the suite, we do:</p>
<pre><code class="lang-bash">yarn mocha round-linked-queue.test.js
</code></pre>
<p>Keep moving and we must ensure that a queue is created empty:</p>
<pre><code class="lang-js">it(<span class="hljs-string">"Should initially set the length to zero"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queueLength = <span class="hljs-number">3</span>;

  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(queueLength);

  expect(queue.length).to.equal(<span class="hljs-number">0</span>);
});
</code></pre>
<blockquote>
<p> <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/2nd-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>In order to make the new test pass, we can do as follows:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-keyword">constructor</span>(maxLength) {
    <span class="hljs-keyword">this</span>._maxLength = maxLength;
    <span class="hljs-keyword">this</span>._length = <span class="hljs-number">0</span>;
  }

  get maxLength() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._maxLength;
  }

  get length() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._length;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/2nd-test-case/round-linked-queue.js">Source</a> and  <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/2nd-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a>.</p>
</blockquote>
<h3 id="testing-adding-elements">Testing adding elements</h3>
<p>Next we create another test suite inside the top-level suite to test the behavior of adding elements to a queue.</p>
<p>Our base use case happens when the queue is empty and we want to add an element to it:</p>
<pre><code class="lang-js">describe(<span class="hljs-string">"When adding elements"</span>, () =&gt; {
  it(<span class="hljs-string">"Should add an element to an empty queue"</span>, () =&gt; {
    <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);
    <span class="hljs-keyword">const</span> originalLength = queue.length;
    <span class="hljs-keyword">const</span> elementToAdd = <span class="hljs-number">1</span>;

    queue.add(elementToAdd);

    <span class="hljs-comment">// Element should've been added to the end of the queue</span>
    expect(queue.last).to.equal(elementToAdd);
    <span class="hljs-comment">// But since it is now the only element, it should also be the at beginning as well</span>
    expect(queue.first).to.equal(elementToAdd);
    <span class="hljs-comment">// Length should've been increased by 1</span>
    expect(queue.length).to.equal(originalLength + <span class="hljs-number">1</span>);
  });
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/3rd-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>If you run the test suite right now, you will get the following error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580928879821/0hl-ZqR1j.png" alt="01-failing-add.png"></p>
<blockquote>
<p>D&#39;oh!</p>
</blockquote>
<p>The test failed because we didn&#39;t implement the <code>add</code> method yet. Now we add <strong>just enough code to make this first test case pass</strong>.</p>
<p><strong>Important:</strong> the code bellow is not entirely correct, we will have to modify it further in order to make the <code>add</code> method work as expected. However, it does make our first test case &quot;adding element to an empty queue&quot; pass.</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  add(element) {
    <span class="hljs-keyword">this</span>._root = element;
    <span class="hljs-keyword">this</span>._first = element;
    <span class="hljs-keyword">this</span>._last = element;

    <span class="hljs-keyword">this</span>._length += <span class="hljs-number">1</span>;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/3rd-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/3rd-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a></p>
</blockquote>
<p>Now let&#39;s try adding a test for when the queue is not empty anymore and yet we still want to add an element to it:</p>
<pre><code class="lang-js">it(<span class="hljs-string">"Should add an element to the end of a non-empty queue"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);
  <span class="hljs-keyword">const</span> previousElement = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">const</span> elementToAdd = <span class="hljs-number">2</span>;
  <span class="hljs-comment">// Make the queue non-empty</span>
  queue.add(previousElement);

  queue.add(elementToAdd);

  <span class="hljs-comment">// Element should've been added to the end of the queue</span>
  expect(queue.last).to.equal(elementToAdd, <span class="hljs-string">"last not properly set"</span>);
  <span class="hljs-comment">// But the first pointer must remain the first element added</span>
  expect(queue.first).to.equal(previousElement, <span class="hljs-string">"first not properly set"</span>);
  <span class="hljs-comment">// Length should've been increased by 2</span>
  expect(queue.length).to.equal(<span class="hljs-number">2</span>, <span class="hljs-string">"length not properly set"</span>);
});
</code></pre>
<p>If we once again run the test suite without changing the implementation, we will get a failure:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580929515125/tL0rKinye.png" alt="02-failing-add-multiple.png"></p>
<p>The more attentive readers should probably be expecting this error because the way we implemented the <code>add</code> method before would simply overwrite the elements in the queue. To fix this, we will need some more code:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  add(element) {
    <span class="hljs-keyword">const</span> node = {
      data: element,
      next: <span class="hljs-literal">null</span>,
    };

    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>._root) {
      <span class="hljs-keyword">this</span>._root = node;
      <span class="hljs-keyword">this</span>._first = node;
      <span class="hljs-keyword">this</span>._last = node;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> previousLast = <span class="hljs-keyword">this</span>._last;
      previousLast.next = node;

      <span class="hljs-keyword">this</span>._last = node;
    }

    <span class="hljs-keyword">this</span>._length += <span class="hljs-number">1</span>;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/4th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/4th-test-case">diff</a></p>
</blockquote>
<p>We had to convert our <code>_root</code>, <code>_first</code> and <code>_last</code> into a <code>node</code> object containing <code>data</code> &mdash; the actual value of the item &mdash; and <code>next</code> &mdash; a pointer to the next <code>node</code> in the linked list.</p>
<p>Moving on, now it&#39;s time to something a little bit more challenging. Whenever our queue is at capacity, adding a new element should should cause the removal of the element that was first added:</p>
<pre><code class="lang-js">it(<span class="hljs-string">"Should remove the first element and add the new element to the end of a full queue"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);
  queue.add(<span class="hljs-number">1</span>);
  queue.add(<span class="hljs-number">2</span>);
  queue.add(<span class="hljs-number">3</span>);

  queue.add(<span class="hljs-number">4</span>);

  <span class="hljs-comment">// Element should've been added to the end of the queue</span>
  expect(queue.last).to.equal(<span class="hljs-number">4</span>, <span class="hljs-string">"last not properly set"</span>);
  <span class="hljs-comment">// The second element should've been shifted to the first position</span>
  expect(queue.first).to.equal(<span class="hljs-number">2</span>, <span class="hljs-string">"first not properly set"</span>);
  <span class="hljs-comment">// Length should still be the same</span>
  expect(queue.length).to.equal(<span class="hljs-number">3</span>, <span class="hljs-string">"length not properly set"</span>);
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/5th-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>Running tests once more we get:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580933433526/6xu7iTV2A.png" alt="03-failing-add-full.png"></p>
<p>Looks like we will need some conditionals to make the new test case pass along with the previous ones:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  add(element) {
    <span class="hljs-keyword">const</span> node = {
      data: element,
      next: <span class="hljs-literal">null</span>,
    };

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.length &lt; <span class="hljs-keyword">this</span>.maxLength) {
      <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>._root) {
        <span class="hljs-keyword">this</span>._root = node;
        <span class="hljs-keyword">this</span>._first = node;
        <span class="hljs-keyword">this</span>._last = node;
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">const</span> previousLast = <span class="hljs-keyword">this</span>._last;
        previousLast.next = node;

        <span class="hljs-keyword">this</span>._last = node;
      }

      <span class="hljs-keyword">this</span>._length += <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">this</span>._root = <span class="hljs-keyword">this</span>._root.next;
      <span class="hljs-keyword">this</span>._last.next = node;
      <span class="hljs-keyword">this</span>._first = <span class="hljs-keyword">this</span>._root;
      <span class="hljs-keyword">this</span>._last = node;
    }
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/5th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/5th-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a></p>
</blockquote>
<h4 id="halt-refactor-time">Halt! Refactor time</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580930165372/nccviHTqB.webp" alt="transformer.webp"></p>
<p>So far we were writing code in a rather linear fashion: make a failing test, implement code to make it pass; make another failing test, write just enough code to make it pass, and so on.</p>
<p>In TDD jargon, creating a failing test is called the <strong>red phase</strong>, while implementing the code that will make it pass is the <strong>green phase</strong>.</p>
<p>In reality, things are not so pretty-neaty. You will not always get how to write the best code possible the first time. The truth is we&#39;ve been cheating a little: we were skipping the <strong>refactor</strong> phase of the TDD cycle:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580931502419/ACYP1JqUi.jpeg" alt="gist-of-tdd.jpg"></p>
<blockquote>
<p>The gist of TDD</p>
</blockquote>
<p>Right now I see some possible improvements in our data structure:</p>
<ol>
<li>Having both <code>_root</code> and <code>_first</code> properties seem redundant.</li>
<li>There is some duplication of code in the <code>add</code> method (remember <a target='_blank' rel='noopener noreferrer'  href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>?)</li>
</ol>
<p>Because we already know the expected behavior, which is coded in our test suite, we are comfortable to <a target='_blank' rel='noopener noreferrer'  href="http://www.extremeprogramming.org/rules/refactor.html">refactor mercilessly</a>.</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  add(element) {
    <span class="hljs-keyword">const</span> node = {
      data: element,
      next: <span class="hljs-literal">null</span>,
    };

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.length &lt; <span class="hljs-keyword">this</span>.maxLength) {
      <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>._first) {
        <span class="hljs-keyword">this</span>._first = node;
        <span class="hljs-keyword">this</span>._last = node;
      }

      <span class="hljs-keyword">this</span>._length += <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">this</span>._first = <span class="hljs-keyword">this</span>._first.next;
    }

    <span class="hljs-keyword">this</span>._last.next = node;
    <span class="hljs-keyword">this</span>._last = node;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/5th-test-case-1st-refactor/round-linked-queue.js">Source</a>  and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/5th-test-case-1st-refactor#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a></p>
</blockquote>
<p>Hopefully, our tests are still green:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580933540191/uLuSISdyy.png" alt="04-successfull-after-refactor.png"></p>
<h4 id="taking-some-shortcuts">Taking some shortcuts</h4>
<p>Now we are going to cheat a little bit. </p>
<p>The last requirement is that the <code>add</code> method should return the removed element when the queue is full. What to return when the queue is not full is not in the specification though. In JavaScript, uninitialized values have a special value called <code>undefined</code>. It makes sense to return that when adding to the queue does not remove any element, so we can add the following two test cases.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580933298455/KGvC42Pai.webp" alt="sheldon-triggered.webp"></p>
<blockquote>
<p>TDD purists gonna be triggered.</p>
</blockquote>
<pre><code class="lang-js">it(<span class="hljs-string">"Should return the removed element from a full queue"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);
  queue.add(<span class="hljs-number">1</span>);
  queue.add(<span class="hljs-number">2</span>);
  queue.add(<span class="hljs-number">3</span>);

  <span class="hljs-keyword">const</span> result = queue.add(<span class="hljs-number">4</span>);

  expect(result).to.equal(<span class="hljs-number">1</span>, <span class="hljs-string">"removed wrong element"</span>);
});

it(<span class="hljs-string">"Should return undefined when the queue is not full"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);

  <span class="hljs-keyword">const</span> result = queue.add(<span class="hljs-number">1</span>);

  expect(result).to.equal(<span class="hljs-literal">undefined</span>, <span class="hljs-string">"should not return an element"</span>);
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/6th-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>Cool, so let&#39;s just return the element from the node we just removed:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  add(element) {
    <span class="hljs-keyword">const</span> node = {
      data: element,
      next: <span class="hljs-literal">null</span>,
    };

    <span class="hljs-keyword">let</span> removedElement;

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.length &lt; <span class="hljs-keyword">this</span>.maxLength) {
      <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>._first) {
        <span class="hljs-keyword">this</span>._first = node;
        <span class="hljs-keyword">this</span>._last = node;
      }

      <span class="hljs-keyword">this</span>._length += <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      removedElement = <span class="hljs-keyword">this</span>._first.data;
      <span class="hljs-keyword">this</span>._first = <span class="hljs-keyword">this</span>._first.next;
    }

    <span class="hljs-keyword">this</span>._last.next = node;
    <span class="hljs-keyword">this</span>._last = node;

    <span class="hljs-keyword">return</span> removedElement;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/6th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/6th-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a></p>
</blockquote>
<p>Look like we are are done with the <code>add method</code>!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580932348080/Pd6-nnNDM.webp" alt="minions-yay.webp"></p>
<h3 id="testing-removing-elements">Testing removing elements</h3>
<p>Removing elements seems like a simpler operation. Our base use case is when the queue is not empty. We remove an element from it and decrease its length by one:</p>
<pre><code class="lang-js">describe(<span class="hljs-string">"When removing elements"</span>, () =&gt; {
  it(<span class="hljs-string">"Should remove the first element of a non-empty queue"</span>, () =&gt; {
    <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);
    queue.add(<span class="hljs-number">1</span>);
    queue.add(<span class="hljs-number">2</span>);
    queue.add(<span class="hljs-number">3</span>);
    <span class="hljs-keyword">const</span> lengthBefore = queue.length;

    <span class="hljs-keyword">const</span> result = queue.remove();

    <span class="hljs-keyword">const</span> lengthAfter = queue.length;

    expect(lengthAfter).to.equal(lengthBefore - <span class="hljs-number">1</span>, <span class="hljs-string">"length should decrease by 1"</span>);
    expect(result).to.equal(<span class="hljs-number">1</span>, <span class="hljs-string">"first element should the one being removed"</span>);
    expect(queue.first).to.equal(<span class="hljs-number">2</span>, <span class="hljs-string">"should shift the second element to the head of the queue"</span>);
    expect(queue.last).to.equal(<span class="hljs-number">3</span>, <span class="hljs-string">"should not change the last element"</span>);
  });
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/7th-test-case/round-linked-queue.test.js">Source</a> </p>
</blockquote>
<p>Running the tests will once again give us an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580934071054/5gRhh90ib.png" alt="05-failing-remove.png"></p>
<p>Now we add some code just to make the test pass:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  remove() {
    <span class="hljs-keyword">const</span> removedElement = <span class="hljs-keyword">this</span>.first;

    <span class="hljs-keyword">this</span>._first = <span class="hljs-keyword">this</span>._first.next;
    <span class="hljs-keyword">this</span>._length -= <span class="hljs-number">1</span>;

    <span class="hljs-keyword">return</span> removedElement;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/7th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/7th-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a> </p>
</blockquote>
<p>The only other use case is when the queue is empty and we try to remove an element from it. When this happens, the queue should throw an exception:</p>
<pre><code class="lang-js">it(<span class="hljs-string">"Should throw an error when the queue is empty"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);

  expect(() =&gt; queue.remove()).to.throw(<span class="hljs-string">"Cannot remove element from an empty queue"</span>);
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/8th-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>Running the test suite as is:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580934743739/9f2zBy2v7.png" alt="06-failing-remove-throw.png"></p>
<blockquote>
<p>Ouch!</p>
</blockquote>
<p>Adding some conditions to test for emptyness and throw the proper error:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  remove() {
    <span class="hljs-keyword">const</span> removedNode = <span class="hljs-keyword">this</span>._first;
    <span class="hljs-keyword">if</span> (!removedNode) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Cannot remove element from an empty queue"</span>);
    }

    <span class="hljs-keyword">this</span>._first = <span class="hljs-keyword">this</span>._first.next;
    <span class="hljs-keyword">this</span>._length -= <span class="hljs-number">1</span>;

    <span class="hljs-keyword">return</span> removedNode.data;
  }
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/8th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/8th-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a></p>
</blockquote>
<p>And that&#39;s it!</p>
<h3 id="testing-edge-cases">Testing edge cases</h3>
<p>There are still some bugs in or code. When we wrote the <code>add</code> method, we included the <code>first</code> and <code>last</code> getters as well. But what happens if we try to access them when the queue is empty? Let&#39;s find out! <code>first</code> things first (ba dum tsss!):</p>
<pre><code class="lang-js">describe(<span class="hljs-string">"When accessing elements"</span>, () =&gt; {
  it(<span class="hljs-string">"Should throw a proper error when acessing the first element of an empty queue"</span>, () =&gt; {
    <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);

    expect(() =&gt; queue.first).to.throw(<span class="hljs-string">"Cannot access the first element of an empty queue"</span>);
  });
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/9th-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>Running the tests:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580935169896/NpERAVp-L.png" alt="07-failing-first-on-empty.png"></p>
<p>Looks like the error message is not really helpful. In fact, it is a little too low level. Let&#39;s make it better:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  get first() {
    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>._first) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Cannot access the first element of an empty queue"</span>);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._first.data;
  }

  <span class="hljs-comment">// ...</span>
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/9th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/9th-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a>  </p>
</blockquote>
<p>Lastly, for the <code>last</code> getter, we will do the same:</p>
<pre><code class="lang-js">it(<span class="hljs-string">"Should throw a proper error when acessing the last element of an empty queue"</span>, () =&gt; {
  <span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> RoundQueue(<span class="hljs-number">3</span>);

  expect(() =&gt; queue.last).to.throw(<span class="hljs-string">"Cannot access the last element of an empty queue"</span>);
});
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/10th-test-case/round-linked-queue.test.js">Source</a></p>
</blockquote>
<p>First the failing result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580935674581/s3ZGqWSY9.png" alt="08-failing-last-on-empty.png"></p>
<p>Then fixing the code:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoundLinkedQueue</span> </span>{
  <span class="hljs-comment">// ...</span>

  get last() {
    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>._last) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Cannot access the last element of an empty queue"</span>);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._last.data;
  }

  <span class="hljs-comment">// ...</span>
}
</code></pre>
<blockquote>
<p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/blob/10th-test-case/round-linked-queue.js">Source</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/round-linked-queue/commit/10th-test-case#diff-68fb0aba2b1c0ad72bf0d44fa71fe5d1">diff</a></p>
</blockquote>
<p>Aaaaaaand that&#39;s about it!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580935929728/ebLYgaAu0.webp" alt="were-are-done-here.webp"></p>
<h2 id="conclusion">Conclusion</h2>
<p>I tried to make this a comprehensive introduction to TDD with the Node.js/JavaScript ecosystem. The data structure we had to implement here was intentionally simple so we could follow the methodology as much as possible.</p>
<p>When doing TDD in real world applications, things are usually not so linear. You will find yourself struggling from time to time with the design choices you make while writing your tests. It can be a little frustrating in the beginning, but once you get the gist of it, you will develop a &quot;muscle memory&quot; to avoid the most common pitfalls.</p>
<p>TDD is great, but as almost everything in life, it is not a silver bullet.</p>
<p>Be safe out there!</p>
<hr>
<p>T-t-th-tha-that&#39;s i-is a-a-all f-f-fo-f-folks!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580936166941/RIWu_DlPS.webp" alt="thats-all-folks.webp"></p>
<hr>
<p>Did you like what you just read? Why don’t you buy me a beer (or a coffee if it is before 5pm 😅) with  <a target='_blank' rel='noopener noreferrer'  href="https://tippin.me/@hbarcelos909">tippin.me</a>?</p>
]]></content:encoded></item><item><title><![CDATA[Give your logs more context — Part 2]]></title><description><![CDATA[Give your logs more context
Building a contextual logger
This is the continuation of my previous article about logging context. Check it out to better understand the purpose of what we will build.
https://blog.henriquebarcelos.dev/give-your-logs-more...]]></description><link>https://blog.hbarcelos.dev/give-your-logs-more-context-part-2</link><guid isPermaLink="true">https://blog.hbarcelos.dev/give-your-logs-more-context-part-2</guid><category><![CDATA[Node.js]]></category><category><![CDATA[logging]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Tue, 04 Feb 2020 18:17:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1580840287782/OQRDtyERT.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="give-your-logs-more-context">Give your logs more context</h1>
<h2 id="building-a-contextual-logger">Building a contextual logger</h2>
<p>This is the continuation of my previous article about logging context. Check it out to better understand the purpose of what we will build.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://blog.henriquebarcelos.dev/give-your-logs-more-context-7b43ea6b4ae6" data-card-controls="0" data-card-theme="light">https://blog.henriquebarcelos.dev/give-your-logs-more-context-7b43ea6b4ae6</a></div>
<h1 id="tl-dr">TL;DR</h1>
<p>The code we are going to build on this story is on my <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hbarcelos/give-your-logs-more-context">Github</a>. If you just want to check out the final version, you can get it at the <code>master</code> branch.</p>
<h1 id="intro">Intro</h1>
<p>Last time we walked through a way of managing context through concurrent requests using <a target='_blank' rel='noopener noreferrer'  href="https://github.com/pinojs/pino"><code>pino</code></a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/jeff-lewis/cls-hooked]"><code>cls-hooked</code></a>. Now let’s build a wrapper around <code>pino</code> that will automatically deal with this for us.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580839364396/sXqNF6Lzz.jpeg" alt="bruce-buffer.jpeg"></p>
<p>And now, it’s tiiiiiiiime!</p>
<h1 id="what-do-we-want-to-achieve-">What do we want to achieve?</h1>
<p>We need to build a logger that will have base “global” context through <code>cls-hooked</code>, but will also allow us to augment such context when actually calling the logger methods.</p>
<p>To improve reusability and interoperability, we want to maintain the original default <code>pino</code> <a target='_blank' rel='noopener noreferrer'  href="https://github.com/pinojs/pino/blob/master/docs/api.md">API</a>, so we already have a good set of test cases to cover. Also, we need to provide a way for our application interact with the context.</p>
<h1 id="how-will-we-write-our-code-">How will we write our code?</h1>
<p>We are going to implement this wrapper TDD style. However the tests we will write are not “unit” tests in a strict sense, because they will include <code>pino</code> itself and make assertions about the generated log data. This is possible because <code>pino</code> accepts a custom <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/stream.html#stream_writable_streams"><code>WritableStream</code></a> as its destination.</p>
<p>As testing framework, we will use <a target='_blank' rel='noopener noreferrer'  href="https://github.com/avajs/ava"><code>ava</code></a>. Keep in mind that while <code>ava</code> transpiles test files by default, it doesn’t do that for the actual code without properly setting <code>babel</code>. To avoid adding more complexity to this solution, all code (including tests) will not use ES modules or any features that are not available in Node.js 10.9.0.</p>
<p>If you want do follow along the implementation, please check out the instructions in the Github repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://github.com/hbarcelos/give-your-logs-more-context" data-card-controls="0" data-card-theme="light">https://github.com/hbarcelos/give-your-logs-more-context</a></div>
<p>I tried to make this the sequence as natural as possible, only eliminating some inner loops and struggles that happen in a regular coding session.</p>
<h1 id="implementation-steps">Implementation steps</h1>
<h2 id="initial-setup">Initial setup</h2>
<pre><code class="lang-bash">yarn init -y
yarn add pino cls-hooked
yarn add --dev ava
</code></pre>
<p>A nice feature of <code>pino</code> accepts a custom <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/stream.html#stream_writable_streams"><code>WritableStream</code></a> as its destination. This will make our lives easier when testing our custom logger.</p>
<h2 id="ensuring-methods-for-log-levels">Ensuring methods for log levels</h2>
<p>For simplicity, lets stick with <code>pino</code> default log levels: <code>trace</code>, <code>debug</code>, <code>info</code>, <code>warn</code>, <code>error</code> and <code>fatal</code>.</p>
<p>The simplest way to achieve that is:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="62c2e8bf5dfb20806204eef4303d1e59"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/62c2e8bf5dfb20806204eef4303d1e59" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/62c2e8bf5dfb20806204eef4303d1e59</a></div><p>The <code>logger.js</code> is currently just a factory function that return plain <code>pino</code> instances. The <code>logger.test.js</code> file generates one test case for each available method to make sure we don’t break anything later.</p>
<p><code>parse-json-stream.js</code> is a utility that will parse the log output stream and return plain Javascript objects to make it easier to run assertions against the log output.</p>
<p><code>stream-to-generator.js</code> is there for convenience: <code>ava</code> doesn’t play well with stream-based APIs. To make tests more concise, we convert the logging stream to a <a target='_blank' rel='noopener noreferrer'  href="http://2ality.com/2015/03/es6-generators.html">generator</a> that yields promises to the next log entry.</p>
<p>The later two are not important in the context of what we are trying to achieve, they are here only for reference. The remaining snippets won’t include them.</p>
<h2 id="keeping-context-on-logger-method-call">Keeping context on logger method call</h2>
<p>Also, notice that <code>pino</code> allow us to pass local context to a log entry by prepending an object to the argument list. This is a behavior we want to keep.</p>
<p>So, lets add a test case that covers this scenario:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="f5274dc0233357a1de1d216d5b06eb9b"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/f5274dc0233357a1de1d216d5b06eb9b" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/f5274dc0233357a1de1d216d5b06eb9b</a></div><p>Since so far we are just creating a <code>pino</code> instance, the test will pass.</p>
<h2 id="adding-cls-awareness">Adding CLS awareness</h2>
<p>Now we start touching CLS. First we need to create namespace and expose it to the world:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="44ea8013acefcc36afab34cba1e133c6"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/44ea8013acefcc36afab34cba1e133c6" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/44ea8013acefcc36afab34cba1e133c6</a></div><h2 id="preventing-cls-context-sharing-between-instances">Preventing CLS context sharing between instances</h2>
<p>For some reason, we might want to have multiple loggers in a given application. When doing that, it’s important to not mix the namespaces of both. However, the way we implemented above, all instances will have the same namespace <code>&#39;@@logger&#39;</code>, which could cause strange behavior laters.</p>
<p>The easiest way to fix this would be to have a <code>counter</code> variable that would increment whenever we call <code>createLogger</code> and append the counter value to the namespace name.</p>
<p>While counters are not the most safe bet to generate unique names, since they are reset when the application restarts, they work in this case because all logger instances would be recreated anyway when the server restarts. Also, this value is not exposed anywhere, it serves only for the purpose of creating different namespaces, so we are fine.</p>
<blockquote>
<p>Sometimes less is more!</p>
</blockquote>
<p>Here’s what’s changed:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="a276e1cad0e3813e40536627188b25ba"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/a276e1cad0e3813e40536627188b25ba" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/a276e1cad0e3813e40536627188b25ba</a></div><h2 id="applying-cls-context-to-logs">Applying CLS context to logs</h2>
<p>This one is a big leap, so bear with me. First, let’s see the changes in the code, then let’s discuss it:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="a351b95fd869240f87cd12d5ad202742"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742</a></div><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580839917043/7noJIXQB9.jpeg" alt="such-wow.jpeg"></p>
<p>Sorry, I couldn’t break this into smaller changes :/</p>
<p>The test code has nothing special about it, just notice that we must run our logging and assertion within the <code>logger.cls.run</code> method callback.</p>
<p>Things start to get interesting on the actual code though. We are leveraging Javascript <a target='_blank' rel='noopener noreferrer'  href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a> to intercept log method calls and patch their arguments.</p>
<p>So, in <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L52">line 52</a> we create a proxy for our logger object, whose handler is named a <code>loggerObjectHandler</code>— <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L34-L43">lines 34–43</a>. The handler defines a <a target='_blank' rel='noopener noreferrer'  href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get"><code>get</code></a> <a target='_blank' rel='noopener noreferrer'  href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get">trap</a>, that will intercept only the calls for the log methods — <code>trace</code>, <code>debug</code>, etc. What it does is wrap those methods into yet another proxy, whose handler is named <code>logMethodHandler</code> — <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L11-L32">lines 11–32</a>.</p>
<p>The <code>loggerMethodHandler</code> gathers the current active context on CLS, excluding some irrelevant properties from it — <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L14-L15">lines 14–15</a>. Then, based on the current argument list, it checks whether we have or not a local context on the method call. If we don’t, then we simply need to prepend the CLS context to the argument list — lines <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L20-L23">20–23</a>. Otherwise, we need to merge the local context into the CLS context — <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L24-L28">lines 24–28</a>. Finally, we call the original method with the proper arguments — <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/a351b95fd869240f87cd12d5ad202742#file-logger-js-diff-L30">line 30</a>.</p>
<h2 id="propagating-changes-to-child-loggers">Propagating changes to child loggers</h2>
<p>A nice feature from <code>pino</code> is that it allows us to create child loggers through the <code>.child()</code> method. A child logger maintains all properties from its parent, but can also accept additional context. So, we need to make our child generation CLS aware too:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3c0d258329696e306c10ba3d3451d871"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/3c0d258329696e306c10ba3d3451d871" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/3c0d258329696e306c10ba3d3451d871</a></div><p>Again, the new tests are self-descriptive. Let’s focus on the implementation. First we extracted the wrapper creation into its own function, named <code>createWrapper</code> — lines <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/3c0d258329696e306c10ba3d3451d871#file-logger-js-diff-L47-L52">47–52</a>. This allows us to create a wrapper for the child loggers as well.</p>
<p>Next, we define a <code>childMethodHandler</code> which will intercept the calls to <code>.child()</code> — <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/3c0d258329696e306c10ba3d3451d871#file-logger-js-diff-L18-L25">lines 18–25</a>. This handler will call <code>createWrapper</code> on the newly created child logger, passing the CLS context from the parent as a parameter. This will guarantee that parent and children (and children of children) all have the same context.</p>
<p>Lastly, we change the implementation of <code>loggerObjectHandler</code> to include the proxy for the <code>.child()</code> method as well — <a target='_blank' rel='noopener noreferrer'  href="https://gist.github.com/hbarcelos/3c0d258329696e306c10ba3d3451d871#file-logger-js-diff-L30-L45">lines 30–45</a> — including some internal refactoring on the conditionals.</p>
<h2 id="further-improvements">Further improvements</h2>
<p>Seems like our code works so far, but it might not be optimal. An issue that is easy to spot is that we are creating new proxies on the fly for every call on the child and log methods. While this might not be an issue with the former — because we wouldn’t call <code>.child()</code> very often — that’s not true for the latter.</p>
<p>To prevent this problem, we could create the proxies for the desired methods by the time we create the logger itself and put them as properties of the logger object. When we call the methods, the <code>loggerObjectHandler</code> would just check to see if there is a proxy set for the current method. If so, it returns the proxy, otherwise, it returns the original property:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3931682d74d7fe2bddf1ba436450832a"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/3931682d74d7fe2bddf1ba436450832a" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/3931682d74d7fe2bddf1ba436450832a</a></div><h1 id="integrating-with-our-web-application">Integrating with our web application</h1>
<p>So now we have our logger factory. Now we need to integrate it with our application. From the final example from the <a target='_blank' rel='noopener noreferrer'  href="/give-your-logs-more-context-7b43ea6b4ae6">previous article</a>, we could refactor to:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3a2c5ff5c3ddbef84f56a9febb4ccdb6"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/3a2c5ff5c3ddbef84f56a9febb4ccdb6" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/3a2c5ff5c3ddbef84f56a9febb4ccdb6</a></div><h1 id="outro">Outro</h1>
<p>The code above is pretty much the same I have successfully used in production some times and have saved me and the teams I worked with a lot of time while debugging.</p>
<p>If you have any suggestions that could improve it, you are more than welcome.</p>
<hr>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580840219703/NfI1RO7Cs.jpeg" alt="deadpool-thats-all-folks.jpeg"></p>
<p>Did you like what you just read? Buy me a beer with <a target='_blank' rel='noopener noreferrer'  href="https://tippin.me/@hbarcelos909">tippin.me</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Give your logs more context — Part 1]]></title><description><![CDATA[Give your logs more context
How to make sense out of your Node.js web app logs
Logging might be one of the most difficult things to do right when building a real world application. Log too little and you will be staring at your screen trying to make ...]]></description><link>https://blog.hbarcelos.dev/give-your-logs-more-context</link><guid isPermaLink="true">https://blog.hbarcelos.dev/give-your-logs-more-context</guid><category><![CDATA[Node.js]]></category><category><![CDATA[logging]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Tue, 04 Feb 2020 17:55:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1580841283351/V4941F11T.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="give-your-logs-more-context">Give your logs more context</h1>
<h2 id="how-to-make-sense-out-of-your-node-js-web-app-logs">How to make sense out of your Node.js web app logs</h2>
<p>Logging might be one of the most difficult things to do right when building a real world application. Log too little and you will be staring at your screen trying to make sense of them (or the charts generated from them). Log too much and you will end up lost in a swamp of useless information, still having no clue if everything is OK or if you have a problem.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580837213455/H12lRhGQX.jpeg" alt="matrix.jpeg">
Logs without the right amount of context look like…</p>
<p>Speaking specifically of the Node.js/Javascript ecosystem, the top 3 logging libraries — <a target='_blank' rel='noopener noreferrer'  href="https://github.com/winstonjs/winston">Winston</a>, <a target='_blank' rel='noopener noreferrer'  href="https://github.com/trentm/node-bunyan">Bunyan</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/pinojs/pino">Pino</a> — can help you to manage context way better than the good ol’ <code>console.log</code> could ever do.</p>
<p>For this article I will use <strong>Pino</strong>, but the ideas can be easily replicated for both Bunyan and Winston (or any other mainstream logging utility).</p>
<h1 id="use-log-levels-wisely">Use log levels wisely</h1>
<p>Pino has 6 default log levels, with increasing severity: <code>trace</code>, <code>debug</code>, <code>info</code>, <code>warn</code>, <code>error</code> and <code>fatal</code>. Each one of these levels maps to an integer from <code>10</code> to <code>60</code>. This makes it easy to analyze your logs later using tools like <a target='_blank' rel='noopener noreferrer'  href="https://stedolan.github.io/jq/"><code>jq</code></a>:</p>
<pre><code class="lang-bash">jq <span class="hljs-string">'select(.level &gt; 40)'</span> <span class="hljs-comment"># gets ERROR and FATAL logs</span>
</code></pre>
<p>While Pino allows you to define custom log levels, I have never seen a use case where they would be necessary, so I tend to stick with the default ones.</p>
<p>Usually, for production, it is recommended to ignore <code>trace</code> and <code>debug</code> levels, unless you are explicitly trying to debug some production issue.</p>
<p>Pino has a <a target='_blank' rel='noopener noreferrer'  href="https://github.com/pinojs/pino/blob/master/docs/api.md#options">configuration option</a> that allows you to define the minimum required level for the log entry to be generated. You can use environment variables to avoid having to make a deploy just to change the log level:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> logger = pino({
  level: process.env.LOG_LEVEL || <span class="hljs-string">'info'</span>
});
</code></pre>
<h2 id="rules-of-thumb">Rules of thumb</h2>
<ul>
<li>Use <code>trace</code> for internal logging that has a potentially high throughput.</li>
<li>Use <code>debug</code> for eventual debugging sessions you might need, but remember to remove them after you are finished.</li>
<li>Use <code>info</code> for regular application workflow logs.</li>
<li>Use <code>warn</code> for expected and frequent error conditions (like user input validation).</li>
<li>Use <code>error</code> for expected but infrequent error conditions (like network failures, database timeouts).</li>
<li>Use <code>fatal</code> for unexpected error conditions.</li>
</ul>
<h1 id="embrace-request-ids">Embrace request IDs</h1>
<p>While we are still developing the application, running unit/integration tests, manually triggering some request to see if everything is running smoothly, it’s all good. The events being produced happen in a more or less predictable order, so it’s easy to follow.</p>
<p>However, once the production version is launched, things can go really crazy. Your app will most certainly process concurrent requests. If you have a few asynchronous steps — like querying a database or calling some external services — the order of each event will be completely unpredictable. In this case, if you are manually inspecting the logs (we all have done this at some point 😅), you can become very frustrated trying to find a thread of execution.</p>
<p>Some frameworks — like <a target='_blank' rel='noopener noreferrer'  href="https://hapijs.com/">Hapi</a> — already take care of this for you. But if you like me still rely on good ol’ <a target='_blank' rel='noopener noreferrer'  href="https://expressjs.com/">express</a>, you have to do it yourself. Defining a middleware that does that is as simple as:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRequestId</span>(<span class="hljs-params">generateId</span>) </span>{
  <span class="hljs-keyword">return</span> (req, res, next) =&gt; {
    req.id = generateId();
    next();
  };
}
</code></pre>
<p>Then use it:</p>
<pre><code><span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> generateId = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> i++;
app.use(setRequestId(generateId));&lt;<span class="hljs-regexp">/span&gt;</span>
</code></pre><p>Of course, this naive implementation would not work if you ever restart your server, since the counter would be reset to <code>0</code>. For a real world application, it’s recommended to use a more robust ID generator, such as <a target='_blank' rel='noopener noreferrer'  href="https://github.com/kelektiv/node-uuid"><code>uuid</code></a> or, my personal choice, <a target='_blank' rel='noopener noreferrer'  href="https://github.com/ericelliott/cuid"><code>cuid</code></a>.</p>
<p>If you use a micro-services architecture (or want to be prepared to), you can leverage distributed tracing simply by allowing your services to forward and receive a given request ID:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setDistributedRequestId</span>(<span class="hljs-params">generateId</span>) </span>{
  <span class="hljs-keyword">return</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> reqId = req.get(<span class="hljs-string">'X-Request-Id'</span>) || generateId();
    req.id = reqId;
    res.set(<span class="hljs-string">'X-RequestId'</span>, reqId);
    next();
  };
}
</code></pre>
<p>Now we can create another middleware that logs incoming requests:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">logIncomingRequests</span>(<span class="hljs-params">logger</span>) </span>{
  <span class="hljs-keyword">return</span> (req, res, next) =&gt; {
    logger.trace({ req, requestId: req.id}, <span class="hljs-string">'Incoming request'</span>);
    next();
  }
}
</code></pre>
<p>And use it:</p>
<pre><code class="lang-js">app.use(logIncommingRequests(pino()))&lt;<span class="hljs-regexp">/span&gt;</span>
</code></pre>
<p>The generated log entry would look like:</p>
<pre><code class="lang-json">{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>, "<span class="hljs-attr">time</span>":<span class="hljs-number">1533749413556</span>, "<span class="hljs-attr">pid</span>":<span class="hljs-number">15377</span>, "<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>, "<span class="hljs-attr">msg</span>":<span class="hljs-string">"Incoming request"</span>, "<span class="hljs-attr">req</span>":{"<span class="hljs-attr">method</span>":<span class="hljs-string">"GET"</span>, "<span class="hljs-attr">url</span>":<span class="hljs-string">"/"</span>, "<span class="hljs-attr">headers</span>":{"<span class="hljs-attr">host</span>":<span class="hljs-string">"localhost:4004"</span>, "<span class="hljs-attr">user-agent</span>":<span class="hljs-string">"curl/7.61.0"</span>, "<span class="hljs-attr">accept</span>":<span class="hljs-string">"*/*"</span>}},
"<span class="hljs-attr">requestId</span>":<span class="hljs-number">1</span>, # &lt;---- notice here!
"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
</code></pre>
<p>So far, so good. We could even use <a target='_blank' rel='noopener noreferrer'  href="https://github.com/pinojs/express-pino-logger"><code>express-pino-logger</code></a> to further integrate the logger with our express app. The major problem here is that the request ID is tightly coupled with our web layer. Unless you define all your business logic within express handlers — something I urge you to <a target='_blank' rel='noopener noreferrer'  href="https://github.com/i0natan/nodebestpractices/blob/master/sections/projectstructre/createlayers.md">please don’t</a> — you won’t be able to access the request ID value in other layers.</p>
<blockquote>
<p>Oh, I could store the IDs in-memory or in a Redis cache and then retrieve it when logging something in other layers!!!</p>
</blockquote>
<p>Yeah, nice try. I thought that myself too, but it doesn’t work. The reason is that you can’t know which request you are currently processing when you have concurrent accesses. Or can you?</p>
<h1 id="meet-continuation-local-storage">Meet Continuation Local Storage</h1>
<p>Imagine that each request is an isolated “thread” of connected execution paths (function calls) that is discarded when the result of the original call is returned.</p>
<p>While <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/">Javascript doesn’t spawn real threads for handling user requests</a>, it emulates this by registering callbacks that will be called in the proper sequence when the results of the function calls are available.</p>
<p>Luckily for us, Node.js <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/async_hooks.html">provides a way</a> to intercept the hops through this execution “thread”. <a target='_blank' rel='noopener noreferrer'  href="https://github.com/jeff-lewis/cls-hooked">Continuation Local Storage</a> (or CLS for short) leverages this capability to keep data available within a given “thread”.</p>
<blockquote>
<p>When you set values in continuation-local storage, those values are accessible until all functions called from the original function — synchronously or asynchronously — have finished executing. This includes callbacks passed to <code>process.nextTick</code> and the <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/timers.html">timer functions</a> (<a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/timers.html#timers_setimmediate_callback_arg">setImmediate</a>, <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg">setTimeout</a>, and <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_arg">setInterval</a>), as well as callbacks passed to asynchronous functions that call native functions (such as those exported from the <code>fs</code>, <code>dns</code>, <code>zlib</code> and <code>crypto</code> modules).</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580838265796/nZCY0GOFe.webp" alt="giphy.webp">
Me when I first discovered CLS…</p>
<p>Redefining our request ID middleware, we would have something like:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createNamespace } <span class="hljs-keyword">from</span> <span class="hljs-string">'cls-hooked'</span>;
<span class="hljs-keyword">import</span> cuid <span class="hljs-keyword">from</span> <span class="hljs-string">'cuid'</span>;
<span class="hljs-keyword">const</span> loggerNamespace = createNamespace(<span class="hljs-string">'logger'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clsRequestId</span>(<span class="hljs-params">namespace, generateId</span>) </span>{
  <span class="hljs-keyword">return</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> reqId = req.get(<span class="hljs-string">'X-Request-Id'</span>) || generateId();

    res.set(<span class="hljs-string">'X-RequestId'</span>, reqId);

    namespace.run(() =&gt; {
      namespace.set(<span class="hljs-string">'requestId'</span>, reqId);
      next();
    });
  };
}

app.use(clsRequestId(loggerNamespace, cuid));
</code></pre>
<p>Breaking it down:</p>
<ul>
<li>A <strong>namespace</strong> is roughly the CLS equivalent of a table from a relational database or a collection/key space from a document store. To create one, we simply need to identify it as a string.</li>
<li>Our “high order” middleware <code>clsRequestId</code> now needs two parameters: the namespace and the ID generator function.</li>
<li><code>namespace.run</code> is the function that creates a new context, bounded to the execution “thread”.</li>
<li><code>namespace.set</code> puts the request ID into local storage.</li>
<li><code>next</code> will call the next express handler. <strong>IMPORTANT:</strong> to make this work as expected, <code>next</code> MUST be called inside the <code>namespace.run</code> callback.</li>
</ul>
<p>Now, whenever we need to access this value, we can use <code>getNamespace</code> from <code>cls-hooked</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { getNamespace } <span class="hljs-keyword">from</span> <span class="hljs-string">'cls-hooked'</span>;
<span class="hljs-keyword">import</span> pino <span class="hljs-keyword">from</span> <span class="hljs-string">'pino'</span>;
<span class="hljs-keyword">const</span> logger = pino();

loggerNamespace = getNamespace(<span class="hljs-string">'logger'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ...</span>
  logger.info({ requestId: loggerNamespace.get(<span class="hljs-string">'requestId'</span>) }, <span class="hljs-string">"Some message"</span>);
}
</code></pre>
<p>If function <code>doStuff</code> call was ultimately originated in one of the handlers from the express app which registered that <code>clsRequestId</code> middleware, the value will be available.</p>
<p>Putting everything toghether:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="bf4c87d7ce3034a568323d1f1f90cf0a"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/hbarcelos/bf4c87d7ce3034a568323d1f1f90cf0a" class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" data-card-controls="0" data-card-theme="light">https://gist.github.com/hbarcelos/bf4c87d7ce3034a568323d1f1f90cf0a</a></div><p>Here’s a sample output generated with <a target='_blank' rel='noopener noreferrer'  href="https://github.com/mcollina/autocannon">autocannon</a>:</p>
<pre><code class="lang-json">{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759930690</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"App is running!"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,"<span class="hljs-attr">endpoint</span>":<span class="hljs-string">"http://localhost:4000"</span>,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759933634</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Before"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2awx0000uhwg9qh20e0b"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759933636</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Before"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2awz0001uhwgoyiptfxv"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759935531</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Middle"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2awz0001uhwgoyiptfxv"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759939590</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Middle"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2awx0000uhwg9qh20e0b"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759941222</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"After"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2awz0001uhwgoyiptfxv"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759941228</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Before"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2grw0002uhwgzz14qyb6"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759943632</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Before"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2imo0003uhwgf4dutgz3"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759946244</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Middle"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2grw0002uhwgzz14qyb6"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759949490</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"After"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2awx0000uhwg9qh20e0b"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759951621</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Middle"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2imo0003uhwgf4dutgz3"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759952464</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"After"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2grw0002uhwgzz14qyb6"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759953632</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Before"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2qcg0004uhwgnmgztdr7"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759954665</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"Middle"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2qcg0004uhwgnmgztdr7"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759955140</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"After"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2imo0003uhwgf4dutgz3"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
{"<span class="hljs-attr">level</span>":<span class="hljs-number">30</span>,"<span class="hljs-attr">time</span>":<span class="hljs-number">1533759957183</span>,"<span class="hljs-attr">msg</span>":<span class="hljs-string">"After"</span>,"<span class="hljs-attr">pid</span>":<span class="hljs-number">4985</span>,"<span class="hljs-attr">hostname</span>":<span class="hljs-string">"henrique-pc"</span>,**"<span class="hljs-attr">requestId</span>":<span class="hljs-string">"cjkll2qcg0004uhwgnmgztdr7"</span>**,"<span class="hljs-attr">v</span>":<span class="hljs-number">1</span>}
</code></pre>
<p>If you look closely you will see that, even though the call order of the logger function is non-linear, the <code>requestId</code> for each different request is mantained.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580838598870/yG3pTNe_L.gif" alt="spongebob-magic.gif"></p>
<p>It’s complete maaaagic!</p>
<p>Now, whenever you want to see the logs from a single request in isolation, you can again use <code>jq</code> and run:</p>
<pre><code class="lang-bash">jq <span class="hljs-string">'select(.requestId == "cjkll2qcg0004uhwgnmgztdr7")'</span> &lt;<span class="hljs-built_in">log</span>_file&gt;
</code></pre>
<p>The output will be:</p>
<pre><code class="lang-json">{
  "<span class="hljs-attr">level</span>": <span class="hljs-number">30</span>,
  "<span class="hljs-attr">time</span>": <span class="hljs-number">1533759953632</span>,
  "<span class="hljs-attr">msg</span>": <span class="hljs-string">"Before"</span>,
  "<span class="hljs-attr">pid</span>": <span class="hljs-number">4985</span>,
  "<span class="hljs-attr">hostname</span>": <span class="hljs-string">"henrique-pc"</span>,
  "<span class="hljs-attr">requestId</span>": <span class="hljs-string">"cjkll2qcg0004uhwgnmgztdr7"</span>,
  "<span class="hljs-attr">v</span>": <span class="hljs-number">1</span>
}
{
  "<span class="hljs-attr">level</span>": <span class="hljs-number">30</span>,
  "<span class="hljs-attr">time</span>": <span class="hljs-number">1533759954665</span>,
  "<span class="hljs-attr">msg</span>": <span class="hljs-string">"Middle"</span>,
  "<span class="hljs-attr">pid</span>": <span class="hljs-number">4985</span>,
  "<span class="hljs-attr">hostname</span>": <span class="hljs-string">"henrique-pc"</span>,
  "<span class="hljs-attr">requestId</span>": <span class="hljs-string">"cjkll2qcg0004uhwgnmgztdr7"</span>,
  "<span class="hljs-attr">v</span>": <span class="hljs-number">1</span>
}
{
  "<span class="hljs-attr">level</span>": <span class="hljs-number">30</span>,
  "<span class="hljs-attr">time</span>": <span class="hljs-number">1533759957183</span>,
  "<span class="hljs-attr">msg</span>": <span class="hljs-string">"After"</span>,
  "<span class="hljs-attr">pid</span>": <span class="hljs-number">4985</span>,
  "<span class="hljs-attr">hostname</span>": <span class="hljs-string">"henrique-pc"</span>,
  "<span class="hljs-attr">requestId</span>": <span class="hljs-string">"cjkll2qcg0004uhwgnmgztdr7"</span>,
  "<span class="hljs-attr">v</span>": <span class="hljs-number">1</span>
}
</code></pre>
<h1 id="further-improvements">Further improvements</h1>
<p>While the structure presented in this story works, it’s not practical for everyday use. It would be very tedious having to manually get the namespace and retrieve all values you need like in the example code above:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> namespace = getNamespace(<span class="hljs-string">'logger'</span>);                                                 logger.info({ requestId: namespace.get(<span class="hljs-string">'requestId'</span>) }, <span class="hljs-string">'Before'</span>)&lt;<span class="hljs-regexp">/span&gt;</span>
</code></pre>
<p>Next time we will build a wrapper around <code>pino</code> to handle all of this transparently.</p>
<hr>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1580838853134/YnTa8jzO4.webp" alt="see-you-soon.webp"></p>
<p>Bye!</p>
<p>Did you like what you just read? Buy me a beer with <a target='_blank' rel='noopener noreferrer'  href="https://tippin.me/@hbarcelos909">tippin.me</a></p>
<hr>
<p>Part 2 is now available here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://blog.henriquebarcelos.dev/give-your-logs-more-context-part-2-c2c952724e04" data-card-controls="0" data-card-theme="light">https://blog.henriquebarcelos.dev/give-your-logs-more-context-part-2-c2c952724e04</a></div>
]]></content:encoded></item><item><title><![CDATA[A better approach for testing your Redux code]]></title><description><![CDATA[TL;DR
When testing Redux, here are a few guidelines:
Vanilla Redux

The smallest standalone unit in Redux is the entire state slice. Unit tests should interact with it as a whole.

There is no point in testing reducers, action creators and selectors ...]]></description><link>https://blog.hbarcelos.dev/a-better-approach-for-testing-your-redux-code</link><guid isPermaLink="true">https://blog.hbarcelos.dev/a-better-approach-for-testing-your-redux-code</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Redux]]></category><category><![CDATA[Testing]]></category><category><![CDATA[TDD (Test-driven development)]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Mon, 25 Nov 2019 22:50:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1574568623046/JJmIguO1j.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-tldr">TL;DR</h2>
<p>When testing Redux, here are a few guidelines:</p>
<h3 id="heading-vanilla-redux">Vanilla Redux</h3>
<ul>
<li><p>The smallest standalone unit in Redux is the entire state slice. Unit tests should interact with it as a whole.</p>
</li>
<li><p>There is no point in testing reducers, action creators and selectors in isolation. As they are tightly coupled with each other, isolation gives us little to no value.</p>
</li>
<li><p>Tests should interact with your Redux slice same way your application will: through action creators and selectors.</p>
</li>
<li><p>Avoid assertions like <code>toEqual</code>/<code>toDeepEqual</code> against the state object, as they create coupling between your tests and the state structure.</p>
<ul>
<li>Selectors give you the granularity you need to run spot on assertions.</li>
</ul>
</li>
<li><p>Selectors and action creators should be boring, so they won't require testing.</p>
</li>
<li><p>Your slice is somewhat equivalent to a pure function, which means you don't need any mocking facilities in order to test it.</p>
</li>
</ul>
<h3 id="heading-redux-redux-thunk">Redux + <code>redux-thunk</code></h3>
<ul>
<li><p>Dispatching thunks doesn't have any direct effect. Only after the thunk is called we will have the side-effects we need to make our application work.</p>
</li>
<li><p>Here you can use stubs, spies and sometimes mocks (but <a target="_blank" href="https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a">don't abuse mocks</a>).</p>
</li>
<li><p>Because of the way thunks are structured, the only way to test them is by testing their implementation details.</p>
</li>
<li><p>The strategy when testing thunks is to setup the store, dispatch the thunk and then asserting whether it dispatched the actions you expected in the order you expected or not.</p>
</li>
</ul>
<p>I have created a <a target="_blank" href="https://github.com/hbarcelos/better-redux-tests">repo</a> implementing the ideas above.</p>
<hr />
<h2 id="heading-intro">Intro</h2>
<p>As a Software Engineer, I am always finding ways to get better at my craft. It is not easy. Not at all. Coding is hard enough. Writing good code is even harder.</p>
<p>Then there are tests. I think every single time I start a new project — professionally or just for fun — my ideas on how I should test my code change. Every. Single. Time. This is not necessarily a bad thing as different problems require different solutions, but this still intrigues me a little.</p>
<h2 id="heading-the-problem-with-tests">The Problem with Tests</h2>
<p>As a ~most of the time~ TDD practitioner, I have learned that the main reason we write tests it not to assert the correctness of our code — this is just a cool side effect. The biggest win when writing tests first is that it guides you through the design of the code you will write next. If something is hard to test, there is <em>probably</em> a better way to implement it.</p>
<p>However, after if you have done this for some time, you realize that writing good tests are as hard as writing production code. Sometimes is even harder. Writing tests takes time. And extra time is something that your clients or the business people in your company will not give you so easily.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1574465138087/M7XYT_x1t.jpeg" alt="Hourglass" /></p>
<blockquote>
<p>Testing?! Ain't nobody got time for that! (Photo by Aron Visuals on Unsplash)</p>
</blockquote>
<p>And it gets worse. Even if you are able to write proper tests, throughout the lifespan of the product/project you are working on, requirements will change, new scenarios will appear. Write too many tests, make them very entangled and any minor change in your application will take a lot of effort to make all tests pass again.</p>
<p>Flaky tests are yet another problem. When it fails, you have no idea were to start fixing it. You will probably just re-run the test suite and if it passes, you are good to go.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1574466460701/jG8dcUyjJ.jpeg" alt="Schrödinger's Paradox" /></p>
<blockquote>
<p>Schrödinger's tests: sometimes they fail, sometimes they pass, but you cannot know for sure (Picture by Jie Qi on Flickr)</p>
</blockquote>
<p>But how do you know if you are writing good tests? What the hell is a good test in the first place?</p>
<h2 id="heading-schools-of-testing">Schools of Testing</h2>
<p>There is an long debate between two different currents of thoughts known as London School and Detroit School of Testing.</p>
<p>Summarizing their differences, while Detroit defends that software should be built bottom-up, with emphasis on design patterns and the <a target="_blank" href="https://en.wikipedia.org/wiki/Black-box_testing">tests should have as little knowledge as possible about the implementation</a> and have little to no stubbing/mocking at all, London advocates that the design should be top-down, using external constraints as starting point, ensuring maximum isolation between test suites through extensive use of stubs/mocks, which has a side effect of <a target="_blank" href="https://en.wikipedia.org/wiki/White-box_testing">having to know how the subject under test is implemented</a>.</p>
<p>This is a very brief summary — even risking being wrong because of terseness — but you can find more good references about this two decades old conundrum <a target="_blank" href="https://github.com/testdouble/contributing-tests/wiki/Detroit-school-TDD">here</a>, <a target="_blank" href="https://github.com/testdouble/contributing-tests/wiki/London-school-TDD">here</a> and <a target="_blank" href="https://medium.com/@adrianbooth/test-driven-development-wars-detroit-vs-london-classicist-vs-mockist-9956c78ae95f">here</a>.</p>
<h2 id="heading-testing-in-the-real-world">Testing in the Real World</h2>
<p>So which one is right, Londoners or Detroitians? <a target="_blank" href="https://blog.ncrunch.net/post/london-tdd-vs-detroit-tdd.aspx">Both of them and neither of them at the same time</a> . As I learnt throughout the almost five years I have been a professional Software Engineer, dogmatism will not take you very far in the real world, where projects should be delivered, product expectations are to be matched and you have bills to pay.</p>
<p>What you really need is to be able to take the <a target="_blank" href="https://blog.ncrunch.net/post/london-tdd-vs-detroit-tdd.aspx">best of both worlds</a> and use it in your favor. Use it wisely.</p>
<p>We live in a world where everybody seems obsessed with ~almost~ perfect code coverage, while the problem of <a target="_blank" href="https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage">Redundant Coverage</a> is rarely mentioned — it is not very easy to find online references discussing this. If you abuse tests, you may end up having a hard time when your requirements suddenly change.</p>
<p>In the end we are not paid to write tests, we are paid to solve other people's problems through code. Writing tests is expensive and does not add <strong>perceivable</strong> value to the clients/users. One can argue that there is value added by tests, but in my personal experience it is very hard to make non-technical people buy that.</p>
<p>What we as Software Engineers should strive for is to write the minimum amount of tests that yields enough confidence in code quality and correctness — and "enough" is <a target="_blank" href="https://medium.com/javascript-scene/why-cutting-costs-is-expensive-how-9-hour-software-engineers-cost-boeing-billions-b76dbe571957">highly dependent on context</a>.</p>
<h2 id="heading-redux-testing-according-to-the-docs">Redux Testing According to the Docs</h2>
<p>Redux is known to have an outstandingly good documentation. In fact this is true. There is not only API docs and some quick examples, as there are also some valuable best practices advice and even links to more in depth discussions regarding Redux and its ecosystem.</p>
<p>However, I believe that the <a target="_blank" href="https://redux.js.org/recipes/writing-tests">"Writing Tests"</a> section leaves something to be desired.</p>
<h3 id="heading-testing-action-creators">Testing Action Creators</h3>
<p>That section in the docs start with action creators.</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addTodo</span>(<span class="hljs-params">text</span>) </span>{
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">type</span>: <span class="hljs-string">'ADD_TODO'</span>,
    text
  }
}
</code></pre>
<p>Then we can test it like:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> actions <span class="hljs-keyword">from</span> <span class="hljs-string">'../../actions/TodoActions'</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> types <span class="hljs-keyword">from</span> <span class="hljs-string">'../../constants/ActionTypes'</span>

describe(<span class="hljs-string">'actions'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should create an action to add a todo'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> text = <span class="hljs-string">'Finish docs'</span>
    <span class="hljs-keyword">const</span> expectedAction = {
      <span class="hljs-attr">type</span>: types.ADD_TODO,
      text
    }
    expect(actions.addTodo(text)).toEqual(expectedAction)
  })
})
</code></pre>
<p>While the test is correct and passes just fine, the fundamental problem here is that <strong>it does not add much value</strong>. Your regular action creators should be <strong>very boring</strong>, almost declarative code. You do not need tests for that.</p>
<p>Furthermore, if you use helper libraries like <a target="_blank" href="https://github.com/pauldijou/redux-act"><code>redux-act</code></a> or Redux's own <a target="_blank" href="https://github.com/reduxjs/redux-toolkit"><code>@reduxjs/toolkit</code></a> — which you <strong>should</strong> — then there is absolutely no reason at all to write tests for them, as you would be testing the helper libs themselves, which are already tested and, more important, are not even owned by you.</p>
<p>And since action creators can be very prolific in a real app, the amount of test they would require is huge.</p>
<blockquote>
<p>But how can we know for sure our plain-old action creators do not contain silly errors like typos on them?</p>
</blockquote>
<p>Bear with me. More on that later.</p>
<h3 id="heading-testing-reducers">Testing reducers</h3>
<p>In Redux, a reducers is a function which given a state and an action, should produce an entirely new state, without mutating the original one. Reducers are pure functions. Pure functions are like heaven to testers. It should be pretty straightforward, right?</p>
<p>The docs gives us the following example:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { ADD_TODO } <span class="hljs-keyword">from</span> <span class="hljs-string">'../constants/ActionTypes'</span>

<span class="hljs-keyword">const</span> initialState = [
  {
    <span class="hljs-attr">text</span>: <span class="hljs-string">'Use Redux'</span>,
    <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">id</span>: <span class="hljs-number">0</span>
  }
]

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">todos</span>(<span class="hljs-params">state = initialState, action</span>) </span>{
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> ADD_TODO:
      <span class="hljs-keyword">return</span> [
        {
          <span class="hljs-attr">id</span>: state.reduce(<span class="hljs-function">(<span class="hljs-params">maxId, todo</span>) =&gt;</span> <span class="hljs-built_in">Math</span>.max(todo.id, maxId), <span class="hljs-number">-1</span>) + <span class="hljs-number">1</span>,
          <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">text</span>: action.text
        },
        ...state
      ]

    <span class="hljs-attr">default</span>:
      <span class="hljs-keyword">return</span> state
  }
}
</code></pre>
<p>Then the test:</p>
<pre><code class="lang-js">describe(<span class="hljs-string">'todos reducer'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should return the initial state'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(reducer(<span class="hljs-literal">undefined</span>, {})).toEqual([
      {
        <span class="hljs-attr">text</span>: <span class="hljs-string">'Use Redux'</span>,
        <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">id</span>: <span class="hljs-number">0</span>
      }
    ])
  })

  it(<span class="hljs-string">'should handle ADD_TODO'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(
      reducer([], {
        <span class="hljs-attr">type</span>: types.ADD_TODO,
        <span class="hljs-attr">text</span>: <span class="hljs-string">'Run the tests'</span>
      })
    ).toEqual([
      {
        <span class="hljs-attr">text</span>: <span class="hljs-string">'Run the tests'</span>,
        <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">id</span>: <span class="hljs-number">0</span>
      }
    ])

    expect(
      reducer(
        [
          {
            <span class="hljs-attr">text</span>: <span class="hljs-string">'Use Redux'</span>,
            <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
            <span class="hljs-attr">id</span>: <span class="hljs-number">0</span>
          }
        ],
        {
          <span class="hljs-attr">type</span>: types.ADD_TODO,
          <span class="hljs-attr">text</span>: <span class="hljs-string">'Run the tests'</span>
        }
      )
    ).toEqual([
      {
        <span class="hljs-attr">text</span>: <span class="hljs-string">'Run the tests'</span>,
        <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>
      },
      {
        <span class="hljs-attr">text</span>: <span class="hljs-string">'Use Redux'</span>,
        <span class="hljs-attr">completed</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">id</span>: <span class="hljs-number">0</span>
      }
    ])
  })
})
</code></pre>
<p>Let's just ignore the fact that the suggested test case <em>"should handle ADD_TODO" is</em> two tests bundled together — which might freak-out some TDD zealots. Even though in this case I believe it would be best to have different test cases — one for an empty list and the other for a list with some initial values — sometimes this is just fine.</p>
<p>The real issue with those tests is that <strong>they are tightly coupled with the internal structure of the reducer</strong>. More precisely, the tests above are coupled to the state object structure through those <code>.toEqual()</code> assertions.</p>
<p>While this example is rather simple, it is very common for the state of a given slice in Redux to change over time, as new requirements arrive and some unforeseen interactions need to occur. If we write tests like the ones above, they will soon become a maintenance nightmare. Any minimal change in the state structure would demand updating several test cases.</p>
<blockquote>
<p>So how exactly are we supposed to write those tests?</p>
</blockquote>
<h2 id="heading-testing-redux-the-right-way">Testing Redux the right way</h2>
<p><strong>Disclaimer:</strong> I am not saying this is the best or the only way of testing your Redux application, however, I recently concluded that doing it the way I suggest below yields the best cost-benefit that I know of. If you happen to know a better way, please reach out to me through the comments, Twitter, e-mail or smoke signs.</p>
<p>Here is a popular folder structure for Redux applications that is very similar to the ones that can be found in many tutorials and even the official docs:</p>
<pre><code class="lang-plaintext">src
└── store
    ├── auth
    │   ├── actions.js
    │   ├── actionTypes.js
    │   └── reducer.js
    └── documents
        ├── actions.js
        ├── actionTypes.js
        └── reducer.js
</code></pre>
<p>If you are like me and like to have test files colocated with the source code, this structure encourages you to have the following:</p>
<pre><code class="lang-plaintext">src
└── store
    ├── auth
    │   ├── actions.js
    │   ├── actions.test.js
    │   ├── actionTypes.js
    │   ├── reducer.js
    │   └── reducer.test.js
    └── documents
        ├── actions.js
        ├── actions.test.js
        ├── actionTypes.js
        ├── reducer.js
        └── reducer.test.js
</code></pre>
<p>I have already left <code>actionTypes</code> tests out as those files are purely declarative. However, I already explained why action creators should be purely declarative, and therefore should not be tested as well. That leaves us with testing the only reducer itself, but that does not seem quite right.</p>
<p>The problem here is what we understand as being a <em>"unit"</em> in Redux. Most people tend to consider each of the individual files above as being a unit. I believe this is a misconception. Actions, action types and reducers <strong>must</strong> be tightly coupled to each other to function properly. To me, it does not make sense to test those "components" in isolation. They all need to come together to form a slice (e.g.: <code>auth</code> and <code>documents</code> above), which I consider being the smallest standalone piece in Redux architecture.</p>
<p>For that reason, I am fond of the <a target="_blank" href="https://github.com/erikras/ducks-modular-redux">Ducks</a> pattern, even though <a target="_blank" href="https://twitter.com/dan_abramov/status/738405796770353152">it has some caveats</a>. Ducks authors advocates everything regarding a single slice (which they call a <em>"duck"</em>) should be placed in a single file and follow a well-defined export structure.</p>
<p>I usually have a structure that looks like this:</p>
<pre><code class="lang-plaintext">src
└── modules
    ├── auth
    │   ├── authSlice.js
    │   └── authSlice.test.js
    └── documents
        ├── documentsSlice.js
        └── documentsSlice.test.js
</code></pre>
<p>The idea now is to write the least amount of tests possible, while having a good degree of confidence that a particular slice works as expected. The reason why Redux exists in the first place is to help us manipulate the state, providing a single place for our application state to lie in.</p>
<p>In other words, the value Redux provides us is the ability to write and read a state from a centralized place, called the store. Since Redux is based on the <a target="_blank" href="https://facebook.github.io/flux/">Flux Architecture</a>, its regular flow is more or less like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1574562511467/uPZvznF1P.png" alt="Flux Architecture" /></p>
<blockquote>
<p>The Flux Architecture by Eric Eliott on Medium</p>
</blockquote>
<h3 id="heading-redux-testing-strategy">Redux Testing Strategy</h3>
<p>In the end of the day, what we want to test is that we are correctly writing to — through dispatching actions — and reading from the store. The way we do that is by given an initial state, we dispatch some action to the store, let the reducer do its work and then after that, we check the state to see if the changes we expect were made.</p>
<p>However, how can we do that while avoiding the pitfall of having the tests coupled with the state object structure? Simple. <a target="_blank" href="https://medium.com/javascript-scene/10-tips-for-better-redux-architecture-69250425af44#975e">Always use selectors</a>. Even those that would seem dumb.</p>
<p>Selectors are you slice public API for reading data. They can encapsulate your state's internal structure and expose only the data your application needs, at the granularity it needs. <a target="_blank" href="https://github.com/reduxjs/reselect">You can also have computed data and optimize it through memoization</a>.</p>
<p>Similarly, action creators are its public API for writing data.</p>
<p>Still confused? Let's try with some code using <a target="_blank" href="https://redux-toolkit.js.org/"><code>@reduxjs/toolkit</code></a>:</p>
<p>Here is my auth slice:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createSlice, createSelector } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> initialState = {
  <span class="hljs-attr">userName</span>: <span class="hljs-string">''</span>,
  <span class="hljs-attr">token</span>: <span class="hljs-string">''</span>,
};

<span class="hljs-keyword">const</span> authSlice = createSlice({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'auth'</span>,
  initialState,
  <span class="hljs-attr">reducers</span>: {
    signIn(state, action) {
      <span class="hljs-keyword">const</span> { token, userName } = action.payload;

      state.token = token;
      state.userName = userName;
    },
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { signIn } = authSlice.actions;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> authSlice.reducer;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectToken = <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.auth.token;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectUserName = <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.auth.userName;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectIsAuthenticated = createSelector([selectToken], <span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token !== <span class="hljs-string">''</span>);
</code></pre>
<p>Nothing really special about this file. I am using the <code>createSlice</code> helper, which saves me a lot of boilerplate code. The exports structure follows more or less the Ducks Pattern, the main difference being that I don't explicitly export the action types, as they are defined in the <code>type</code> property of the action creators (e.g.: <code>signIn.type</code> returns <code>'auth/signIn'</code>).</p>
<p>Now the test suite implemented using <a target="_blank" href="https://jestjs.io/"><code>jest</code></a>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> reducer, { initialState, signIn, selectToken, selectName, selectIsAuthenticated } <span class="hljs-keyword">from</span> <span class="hljs-string">'./authSlice'</span>;

describe(<span class="hljs-string">'auth slice'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'reducer, actions and selectors'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should return the initial state on first run'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> nextState = initialState;

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">const</span> result = reducer(<span class="hljs-literal">undefined</span>, {});

      <span class="hljs-comment">// Assert</span>
      expect(result).toEqual(nextState);
    });

    it(<span class="hljs-string">'should properly set the state when sign in is made'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> data = {
        <span class="hljs-attr">userName</span>: <span class="hljs-string">'John Doe'</span>,
        <span class="hljs-attr">token</span>: <span class="hljs-string">'This is a valid token. Trust me!'</span>,
      };

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">const</span> nextState = reducer(initialState, signIn(data));

      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> rootState = { <span class="hljs-attr">auth</span>: nextState };
      expect(selectIsAuthenticated(rootState)).toEqual(<span class="hljs-literal">true</span>);
      expect(selectUserName(rootState)).toEqual(data.userName);
      expect(selectToken(rootState)).toEqual(data.token);
    });
  });
});
</code></pre>
<p>The first test case (<code>'should return the initial state on first run'</code>) is only there to ensure there is no problem in the definition of the slice file. Notice that I am using the <code>.toEqual()</code> assertion I said you should not. However, in this case, since the assertion is against the constant <code>initialState</code> and there are no mutations, whenever the state shape changes, <code>initialState</code> changes together, so this test would automatically be "fixed".</p>
<p>The second test case is what we are interested in here. From the initial state, we "dispatch" a <code>signIn</code> action with the expected payload. Then we check if the produced state is what we expected. However, we do that exclusively using selectors. This way our test is more decoupled from the implementation</p>
<p>If your slice grows bigger, by using selectors when testing state transitions, you gain yet another advantage: you could use only those selectors that are affected by the action you dispatched and can ignore everything else. Were you asserting against the full slice state tree, you would still need to declare those unrelated state properties in the assertion.</p>
<p>An observant reader might have noticed that this style of testing resembles more the one derived from <a target="_blank" href="https://github.com/testdouble/contributing-tests/wiki/Detroit-school-TDD">Detroit School</a>. There are no mocks, stubs, spies or whatever. Since reducers are simply pure functions, there is no point in using those.</p>
<p>However, this slice is rather too simple. Authentication is usually tied to some back-end service, which means we have to manage the communication between the latter and our application, that is, we have to handle side effects as well as the loading state. Things start to get more complicated.</p>
<h3 id="heading-testing-a-more-realistic-slice">Testing a More Realistic Slice</h3>
<p>The first step is to split our <code>signIn</code> action into three new: <code>signInStart</code>, <code>signInSuccess</code> and <code>signInFailure</code>. The names should be self-explanatory. After that, our state needs to handle the loading state and an eventual error.</p>
<p>Here is some code with those changes:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createSlice, createSelector } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> initialState = {
  <span class="hljs-attr">isLoading</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">user</span>: {
    <span class="hljs-attr">userName</span>: <span class="hljs-string">''</span>,
    <span class="hljs-attr">token</span>: <span class="hljs-string">''</span>,
  },
  <span class="hljs-attr">error</span>: <span class="hljs-literal">null</span>,
};

<span class="hljs-keyword">const</span> authSlice = createSlice({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'auth'</span>,
  initialState,
  <span class="hljs-attr">reducers</span>: {
    signInStart(state, action) {
      state.isLoading = <span class="hljs-literal">true</span>;
      state.error = <span class="hljs-literal">null</span>;
    },
    signInSuccess(state, action) {
      <span class="hljs-keyword">const</span> { token, userName } = action.payload;

      state.user = { token, userName };
      state.isLoading = <span class="hljs-literal">false</span>;
      state.error = <span class="hljs-literal">null</span>;
    },
    signInFailure(state, action) {
      <span class="hljs-keyword">const</span> { error } = action.payload;

      state.error = error;
      state.user = {
        <span class="hljs-attr">userName</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">token</span>: <span class="hljs-string">''</span>,
      };
      state.isLoading = <span class="hljs-literal">false</span>;
    },
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { signInStart, signInSuccess, signInFailure } = authSlice.actions;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> authSlice.reducer;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectToken = <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.auth.user.token;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectUserName = <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.auth.user.userName;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectError = <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.auth.error;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectIsLoading = <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.auth.isLoading;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> selectIsAuthenticated = createSelector([selectToken], <span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token !== <span class="hljs-string">''</span>);
</code></pre>
<p>The first thing you might notice is that our state shape changed. We nested <code>userName</code> and <code>token</code> in a <code>user</code> property. Had we not created selectors, this would break all the tests and code that depends on this slice. However, since we did have the selectors, the only changes we need to do are in the <code>selectToken</code> and <code>selectUserName</code>.</p>
<p>Notice that our test suite is still broken, but that is because we fundamentally changed the slice. It is not hard to get it fixed though:</p>
<pre><code class="lang-js">describe(<span class="hljs-string">'auth slice'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'reducer, actions and selectors'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should return the initial state on first run'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> nextState = initialState;

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">const</span> result = reducer(<span class="hljs-literal">undefined</span>, {});

      <span class="hljs-comment">// Assert</span>
      expect(result).toEqual(nextState);
    });

    it(<span class="hljs-string">'should properly set loading and error state when a sign in request is made'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">const</span> nextState = reducer(initialState, signInStart());

      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> rootState = { <span class="hljs-attr">auth</span>: nextState };
      expect(selectIsAuthenticated(rootState)).toEqual(<span class="hljs-literal">false</span>);
      expect(selectIsLoading(rootState)).toEqual(<span class="hljs-literal">true</span>);
      expect(selectError(rootState)).toEqual(<span class="hljs-literal">null</span>);
    });

    it(<span class="hljs-string">'should properly set loading, error and user information when a sign in request succeeds'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> payload = { <span class="hljs-attr">token</span>: <span class="hljs-string">'this is a token'</span>, <span class="hljs-attr">userName</span>: <span class="hljs-string">'John Doe'</span> };

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">const</span> nextState = reducer(initialState, signInSuccess(payload));

      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> rootState = { <span class="hljs-attr">auth</span>: nextState };
      expect(selectIsAuthenticated(rootState)).toEqual(<span class="hljs-literal">true</span>);
      expect(selectToken(rootState)).toEqual(payload.token);
      expect(selectUserName(rootState)).toEqual(payload.userName);
      expect(selectIsLoading(rootState)).toEqual(<span class="hljs-literal">false</span>);
      expect(selectError(rootState)).toEqual(<span class="hljs-literal">null</span>);
    });

    it(<span class="hljs-string">'should properly set loading, error and remove user information when sign in request fails'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> error = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Incorrect password'</span>);

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">const</span> nextState = reducer(initialState, signInFailure({ <span class="hljs-attr">error</span>: error.message }));

      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> rootState = { <span class="hljs-attr">auth</span>: nextState };
      expect(selectIsAuthenticated(rootState)).toEqual(<span class="hljs-literal">false</span>);
      expect(selectToken(rootState)).toEqual(<span class="hljs-string">''</span>);
      expect(selectUserName(rootState)).toEqual(<span class="hljs-string">''</span>);
      expect(selectIsLoading(rootState)).toEqual(<span class="hljs-literal">false</span>);
      expect(selectError(rootState)).toEqual(error.message);
    });
  });
});
</code></pre>
<p>Notice that <code>signInStart</code> has less assertions regarding the new state, because current <code>userName</code> and <code>token</code> do not matter to it. Everything else is much in line with what we have discussed so far.</p>
<p>Another subtlety might go unnoticed. Even though the main focus of the tests is the reducer, they end up testing the action creators as well. Those silly errors like typos will get caught here, so we do not need to write a separate suite of tests to prevent them from happening.</p>
<p>The same thing goes for selectors too. Plain selectors are purely declarative code. Memoized selectors for derived data created with <code>createSelector</code> from <a target="_blank" href="https://github.com/reduxjs/reselect">reselect</a> should not be tested as well. Errors will get caught in the reducer test.</p>
<p>For example, if we had forgotten to change <code>selectUserName</code> and <code>selectToken</code> after refactoring the state shape and left them like this:</p>
<pre><code class="lang-plaintext">// should be state.auth.user.token
export const selectToken = state =&gt; state.auth.token;

// should be state.auth.user.userName
export const selectUserName = state =&gt; state.auth.userName;
</code></pre>
<p>In that case, all test cases above would fail.</p>
<h3 id="heading-testing-side-effects">Testing Side-Effects</h3>
<p>We are getting there, but our slice is not complete yet. It lacks the part that orchestrates the sign-in flow and communicates with the back-end service API.</p>
<p>Redux itself deliberately does not handle side effects. To be able to do that, you need a Redux Middleware that will handle that for you. While you can <a target="_blank" href="https://redux.js.org/introduction/ecosystem#side-effects">pick your poison</a>, <code>@reduxjs/toolkit</code> already ships with <code>redux-thunk</code>, so that is what we are going to use.</p>
<p>In this case, the Redux docs has a <a target="_blank" href="https://redux.js.org/recipes/writing-tests#async-action-creators">really good example</a>, so I took it and adapted it to our use case.</p>
<p>In our <code>authSlice.js</code>, we simply add:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> api <span class="hljs-keyword">from</span> <span class="hljs-string">'../../api'</span>;

<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> signIn = <span class="hljs-function">(<span class="hljs-params">{ email, password }</span>) =&gt;</span> <span class="hljs-keyword">async</span> dispatch =&gt; {
  <span class="hljs-keyword">try</span> {
    dispatch(signInStart());
    <span class="hljs-keyword">const</span> { token, userName } = <span class="hljs-keyword">await</span> api.signIn({
      email,
      password,
    });
    dispatch(signInSuccess({ token, userName }));
  } <span class="hljs-keyword">catch</span> (error) {
    dispatch(signInFailure({ error }));
  }
};
</code></pre>
<p>Notice that the <code>signIn</code> function is almost like an action creator, however, instead of returning the action object, it returns a function that receives the dispatch function as a parameter. This is the "action" that will be triggered when the user clicks the "Sign In" button in our application.</p>
<p>This means that functions like <code>signIn</code> are very important to the application, therefore, they should be tested. However, how can we test this in isolation from the <code>api</code> module? Enter Mocks and Stubs.</p>
<p>Since this is an orchestration component, we are not interested in the visible effects it has. Instead, we are interested in the actions that were dispatched from within the thunk according to the response from the API.</p>
<p>So we can change the test file like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> configureMockStore <span class="hljs-keyword">from</span> <span class="hljs-string">'redux-mock-store'</span>;
<span class="hljs-keyword">import</span> thunk <span class="hljs-keyword">from</span> <span class="hljs-string">'redux-thunk'</span>;
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> api <span class="hljs-keyword">from</span> <span class="hljs-string">'../../api'</span>;

jest.mock(<span class="hljs-string">'../../api'</span>);

<span class="hljs-keyword">const</span> mockStore = configureMockStore([thunk]);

describe(<span class="hljs-string">'thunks'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'creates both signInStart and signInSuccess when sign in succeeds'</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> requestPayload = {
        <span class="hljs-attr">email</span>: <span class="hljs-string">'john.doe@example.com'</span>,
        <span class="hljs-attr">password</span>: <span class="hljs-string">'very secret'</span>,
      };
      <span class="hljs-keyword">const</span> responsePayload = {
        <span class="hljs-attr">token</span>: <span class="hljs-string">'this is a token'</span>,
        <span class="hljs-attr">userName</span>: <span class="hljs-string">'John Doe'</span>,
      };
      <span class="hljs-keyword">const</span> store = mockStore(initialState);
      api.signIn.mockResolvedValueOnce(responsePayload);

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">await</span> store.dispatch(signIn(requestPayload));

      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> expectedActions = [signInStart(), signInSuccess(responsePayload)];
      expect(store.getActions()).toEqual(expectedActions);
    });

    it(<span class="hljs-string">'creates both signInStart and signInFailure when sign in fails'</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-comment">// Arrange</span>
      <span class="hljs-keyword">const</span> requestPayload = {
        <span class="hljs-attr">email</span>: <span class="hljs-string">'john.doe@example.com'</span>,
        <span class="hljs-attr">password</span>: <span class="hljs-string">'wrong passoword'</span>,
      };
      <span class="hljs-keyword">const</span> responseError = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid credentials'</span>);
      <span class="hljs-keyword">const</span> store = mockStore(initialState);
      api.signIn.mockRejectedValueOnce(responseError);

      <span class="hljs-comment">// Act</span>
      <span class="hljs-keyword">await</span> store.dispatch(signIn(requestPayload));

      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> expectedActions = [signInStart(), signInFailure({ <span class="hljs-attr">error</span>: responseError })];
      expect(store.getActions()).toEqual(expectedActions);
    });
  });
</code></pre>
<p>So unlike reducers, which are easier to test with Detroit School methodology, we leverage London School style to test our thunks, because that is what makes sense.</p>
<p>Because we are testing implementation details, whenever code changes, our tests must reflect that. In a real-world app, after a successful sign-in, you probably want to redirect the user somewhere. If we were using something like <a target="_blank" href="https://github.com/supasate/connected-react-router">connected-react-router</a>, we would end up with a code like this:</p>
<pre><code class="lang-diff"><span class="hljs-addition">+import { push } from 'connected-react-router';</span>
 // ...
 import api from '../../api';

 // ...
     const { token, userName } = await api.signIn({
       email,
       password,
     });
     dispatch(signInSuccess({ token, userName }));
<span class="hljs-addition">+    dispatch(push('/'));</span>
   } catch (error) {
     dispatch(signInFailure({ error }));
   }
 // ...
</code></pre>
<p>Then we update the assert part of our test case:</p>
<pre><code class="lang-diff"><span class="hljs-addition">+import { push } from 'connected-react-router';</span>
 // ...

 // Assert
 const expectedActions = [
   signInStart(),
   signInSuccess(responsePayload),
<span class="hljs-addition">+  push('/')</span>
 ];
 expect(store.getActions()).toEqual(expectedActions);
 // ...
</code></pre>
<p>This is often a criticism against <code>redux-thunk</code>, but if you decided to use it, that is a trade-off you have to deal with.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When it comes to the real world, there is no single best approach for writing tests. We can and should leverage both Detroit and London styles to effectively test your applications.</p>
<p>For components that behave like pure functions, that is, given some input, produce some deterministic output, Detroit style shines. Our tests can be a little bit more coarse-grained, as having perfect isolation does not add much value to them. Where exactly we should draw the line? Like most good questions, the answer is "It depends".</p>
<p>In Redux, I have concluded that a slice is the smallest standalone unit that exists. It makes little to no sense to write isolated tests for their sub-components, like reducers, action creators and selectors. We test them together. If any of them is broken, the tests will show us and it will be easy to find out which one.</p>
<p>On the other hand, when our components exist solely for orchestration purposes, then London-style tests are the way to go. Since we are testing implementation details, tests should be as fine-grained as they get, leveraging mocks, stubs, spies and whatever else we need. However, this comes with a burden of harder maintainability.</p>
<p>When using <code>redux-thunk</code>, what we should test is that our thunk is dispatching the appropriate actions in the same sequence we would expect. Helpers like <a target="_blank" href="https://github.com/dmitry-zaets/redux-mock-store"><code>redux-mock-store</code></a> eases the task for us, as it exposes more of the internal state of the store than Redux native store.</p>
<p>T-th-tha-that's a-all f-fo-fo-folks!</p>
]]></content:encoded></item><item><title><![CDATA[Things I wish I knew about Terraform before jumping into it]]></title><description><![CDATA[Things I wish I knew about Terraform before jumping into it
A few weeks ago I wrote about my journey into Terraform. You can read more about this quest here:
https://blog.henriquebarcelos.dev/how-i-learnt-to-love-and-hate-terraform-in-the-past-few-we...]]></description><link>https://blog.hbarcelos.dev/things-i-wish-i-knew-about-terraform-before-jumping-into-it</link><guid isPermaLink="true">https://blog.hbarcelos.dev/things-i-wish-i-knew-about-terraform-before-jumping-into-it</guid><category><![CDATA[Terraform]]></category><category><![CDATA[infrastructure]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Wed, 13 Sep 2017 12:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1590677442854/rkDe1Dkis.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="things-i-wish-i-knew-about-terraform-before-jumping-into-it">Things I wish I knew about Terraform before jumping into it</h1>
<p>A few weeks ago I wrote about my journey into Terraform. You can read more about this quest here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://blog.henriquebarcelos.dev/how-i-learnt-to-love-and-hate-terraform-in-the-past-few-weeks-db085d012882" data-card-controls="0" data-card-theme="light">https://blog.henriquebarcelos.dev/how-i-learnt-to-love-and-hate-terraform-in-the-past-few-weeks-db085d012882</a></div>
<p>Now I’ll try to summarize some learnings I had during the process.</p>
<h1 id="foreword">Foreword</h1>
<p><img src="https://miro.medium.com/max/1104/1*NQl__kO1LKXSGpteSSYHug.jpeg" alt="locki-meme"></p>
<blockquote>
<p>It’s ironic that the Portuguese translation of Earth is “Terra”.</p>
</blockquote>
<p>Before really started exploring this infrastructure-as-code world, I’ve heard a lot of blabbering about Terraform.</p>
<blockquote>
<p>— Terraform is really easy, you just write some code and <em>bam!</em>, there is your infrastructure…</p>
<p>— Terraform uses declarative syntax, it can’t be that hard…</p>
<p>— Terraform is cloud-agnostic, write your infrastructure code once and run it on AWS, GoogleCloud, Azure, Bluemix or wherever…</p>
<p>— Terraform can prevent children from starving in sub-Sahan Africa…</p>
</blockquote>
<p>Of course that’s all bullshit!</p>
<p>Don’t get me wrong, I still think Terraform is a fantastic tool once you get to know it in further details, but the learning curve can be very steep, specially if you don’t have a good understanding of how the underlying provider works.</p>
<p>Here are some things I wish I knew before diving into this quest.</p>
<h1 id="terraform-is-not-mature-yet">Terraform is not mature yet</h1>
<p><img src="https://miro.medium.com/max/566/1*eznHc2vCHMlcDMnztRdnbQ.jpeg" alt="children-in-adult-clothes"></p>
<blockquote>
<p>Yo, easy there… You ain’t grown up enough for this!</p>
</blockquote>
<p>Terraform is still under very active development. You can see how serious I am about that by visiting its <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hashicorp/terraform/releases">releases</a> and <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hashicorp/terraform/issues">issues</a> page on Github. By the time I started writing this post, the latest “stable” version available was <code>0.9.11</code> and there were 742 issues open only in the main repo (there are also individual repos for each major cloud provider). I myself must have opened a handful of them in the last few weeks.</p>
<p>Because its major version is still <code>0</code> , it means you can expect significant breaking changes until <code>v1</code> is launched. In fact, that’s the most frustrating part when you are trying to get started with it is that if you find that cool article on the internet about how to do X with Terraform, there are high chances that it will be outdated, even if it is just a few months old.</p>
<p>Beware of this before you start putting all your infrastructure in it.</p>
<h1 id="terraform-is-not-cloud-agnostic">Terraform is not cloud-agnostic</h1>
<p><img src="https://miro.medium.com/max/1000/1*-5tWPG8Ml3sO2BPiAZvoSQ.jpeg" alt="jesus-statue-with-cloudy-sky"></p>
<blockquote>
<p>In the end, we are still all a bunch of cloud-believers…</p>
</blockquote>
<p>I don’t remember where exactly I heard or read this, but this is probably the most widespread fake news™ about Terraform.</p>
<p>While it does provide support for multiple <a target='_blank' rel='noopener noreferrer'  href="https://www.terraform.io/docs/providers/index.html">providers</a> — since full-featured ones, as AWS, to more specific services, such as Github — it’s not like you can describe your infrastructure with generic code that will be translated into provider-specific resources.</p>
<p>In fact, Terraform has resources that maps more or less 1-to-1 to the underlying provider resources, often keeping known jargons as well. For example, an AWS Classic Load Balancer is named <code>aws_elb</code> in Terraform, while the closer equivalent on Microsoft Azure is called <code>azurerm_lb</code> . As you might expect, the configuration parameters for each resource also change, so they are not interchangeable whatsoever.</p>
<p>Therefore, cloud platform migration will continue to suck for the time being, but Terraform can make this task a little (yeah, just a little) less brittle.</p>
<h1 id="terraform-won-t-hide-the-complexity-of-underlying-providers">Terraform won’t hide the complexity of underlying providers</h1>
<p>If you don’t understand how AWS works, Terraform will not make your life easier. Indeed, it might make it worse, because you’ll have to deal with both AWS and Terraform quirks.</p>
<p>AWS has regional and global services. For instance, EC2 is regional — which means that an auto-scaling group in North Virginia has nothing to do with one in São Paulo. Therefore, they can have the same name (which are ASG identifiers). IAM, in its turn, is global, which means that when you define a role, it can be used anywhere. Then there is S3. S3 is a hybrid: while it has regional scope, its namespace is global, which means you can’t have buckets with the same name, even across different regions.</p>
<p>Terraform ain’t gonna help you with that sort of things. When you run <code>teraform plan</code>, it will tell you it’s all ok. Then you will happily proceed to <code>terraform apply</code> and instead of running smoothly as you’d expect, it’s going to blow up on your face and tell you how stupid you are.</p>
<h1 id="-avoid-code-duplication-is-so-old-fashioned">“Avoid code duplication” is so old-fashioned</h1>
<p>No, I’m not and advocate of the RCP design pattern (Reuse by Copy &amp; Paste), but, as far as I know, it’s very hard to keep your code generic with Terraform.</p>
<p>For example, if you are nesting modules, to make a parameter of the innermost one externally configurable, you must lift it up to every module in the middle:</p>
<pre><code><span class="hljs-comment"># A/main.tf</span>

variable <span class="hljs-string">"b"</span> {}

variable <span class="hljs-string">"c"</span> {}

module A {
  <span class="hljs-built_in">source</span>=<span class="hljs-string">"/path/to/module/A"</span>
  b=<span class="hljs-string">"<span class="hljs-variable">${var.b}</span>"</span>
  c=<span class="hljs-string">"<span class="hljs-variable">${var.c}</span>"</span>
}

<span class="hljs-comment"># ...</span>

<span class="hljs-comment"># B/main.tf</span>

variable <span class="hljs-string">"b"</span> {}
variable <span class="hljs-string">"c"</span> {}

module B {
  <span class="hljs-built_in">source</span>=<span class="hljs-string">"/path/to/module/B"</span>
  var_c=<span class="hljs-string">"<span class="hljs-variable">${var.c}</span>"</span>
}

<span class="hljs-comment"># ...</span>

<span class="hljs-comment"># C/main.tf</span>

variable <span class="hljs-string">"c"</span> {}

some_provider <span class="hljs-string">"some_resource"</span> C {
  some_attribute=<span class="hljs-string">"<span class="hljs-variable">${var.c}</span>"</span>
}
</code></pre><p>You have to declare the variable <code>c</code> 3 times and <code>b</code> 2 times to make it work.</p>
<p>Sometimes you are better off shamelessly copying a resource or module and changing it a little bit then trying to extensively parametrize it in order to make it more general. You’ll thank me later 😉.</p>
<p>If you know a better way of doing this, for the sake of God, Budah, Krishna, Goku, etc., please let me know.</p>
<h1 id="terraform-is-full-of-dirty-hacks">Terraform is full of dirty hacks</h1>
<p><a target='_blank' rel='noopener noreferrer'  href="https://www.terraform.io/docs/configuration/syntax.html">HCL</a> (HashiCorp Configuration Language) is the name of the description language used by Terraform (HashiCorp is the company behind it). It has some, let’s say, peculiar ways of solving certain problems.</p>
<p><img src="https://miro.medium.com/max/1104/1*YxqvEyqS0i1qa7MRlzeycg.jpeg" alt="jerry-rigged-pizza-warming"></p>
<blockquote>
<p>Warming a pizza with Terraform feels like…</p>
</blockquote>
<p>One example: suppose that you want to conditionally create a resource, like this:</p>
<pre><code><span class="hljs-attribute">variable</span> <span class="hljs-string">"custom_sg"</span> {
  <span class="hljs-attribute">description</span> = <span class="hljs-string">"Custom security groups for the instance"</span>
  default = <span class="hljs-string">""</span>
}

resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"default_sg"</span> {
  <span class="hljs-attribute">count</span> = <span class="hljs-string">"<span class="hljs-variable">${custom_sg == "" ? 1 : 0}</span> # this is a bit odd, but ok
  # More params bellow... they are not relevant
}

resource "</span>aws_instace<span class="hljs-string">" "</span>example<span class="hljs-string">" {
  securit_groups = ["</span><span class="hljs-variable">${custom_sg != "" ? custom_sg : aws_security_group.default_sg.id}</span><span class="hljs-string">"]
  # More params bellow... they are not relevant
}</span>
</code></pre><p>The code above should work, right? WRONG!</p>
<p>Whenever you use the <code>count</code> parameter in a resource, Terraform will assume it is list of resources, even if the only possible values are <code>0</code> and <code>1</code> . So the code above you crash right in front of your eyes (the good news is that it fails on <code>plan</code> stage).</p>
<p>Well, since your <code>aws_security_group.default_sg</code> is a list, you cannot access its params directly. Instead, you have to point to individual items on that list. Terraform let’s you do that using a syntax like <code>resource.name.&lt;n&gt;.param</code> , where <code>n</code> is a number.</p>
<blockquote>
<p>So I can just declare it like this:</p>
</blockquote>
<pre><code><span class="hljs-selector-tag">aws_security_group</span><span class="hljs-selector-class">.default_sg</span><span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.id</span>
</code></pre><p>Nice try, but you can’t! Because the way HCL is implemented, it has to parse the whole template, even if the value is not used. So in the case that you set <code>default_sg</code> , there is no item in <code>aws_security_group.default</code> to be referenced by <code>0</code>, so Terraform will once again beat you to the ground and then laugh on your crying-baby face.</p>
<p>The “official” workaround is this:</p>
<pre><code>resource <span class="hljs-string">"aws_instace"</span> <span class="hljs-string">"example"</span> {
  securit_groups = [<span class="hljs-string">"<span class="hljs-subst">${custom_sg != <span class="hljs-string">""</span> ? custom_sg : <span class="hljs-keyword">join</span>(<span class="hljs-string">""</span>, aws_security_group.default_sg.*.id)}</span>"</span>] <span class="hljs-comment"># WTF dude?</span>
  <span class="hljs-comment"># More params bellow... they are not relevant</span>
}
</code></pre><p>Seriously? Where the heck did this come from? That <code>*</code> means all elements in the list. When none is present, it returns an empty list. That <code>join</code> is just a regular <code>List -&gt; String</code> function. When the list is empty, it returns an empty string. When it has one element, it returns the parameter I want.</p>
<p>There are lots of dirty little hacks like this. The place to find them is the <a target='_blank' rel='noopener noreferrer'  href="https://github.com/hashicorp/terraform/issues">issues</a> page.</p>
<h1 id="don-t-use-nested-in-line-resources">Don’t use nested/in-line resources</h1>
<p>No, seriously… Don’t do that… this gave me some pain in the arse.</p>
<p>In case you have no idea what I’m talking about, Terraform allows you to define some resources within its “parent” as well as a standalone resource with a reference to it.</p>
<p>The reason why I recommend doing so is because, at least for us, a common use case is the need to extend security groups, route tables and other resources that support in-line resources. And the only way we can do that from outside the module definition is with standalone resources pointing to the resource within the module.</p>
<p>I have defined a VPC module, which contains the basic definition for a VPC. This makes the creation of VPCs on multiple regions a piece of cake. However, our main infrastructure is located in N. Virginia, so its configurations should be a little bit different from the other ones (not different enough to justify code duplication, though).</p>
<p>Initially I had something like this in our VPC module definition:</p>
<pre><code><span class="hljs-comment"># ...</span>
<span class="hljs-attribute">resource</span> <span class="hljs-string">"aws_route_table"</span> <span class="hljs-string">"public"</span> {
  <span class="hljs-attribute">vpc_id</span> = <span class="hljs-string">"<span class="hljs-variable">${aws_vpc.cluster_vpc.id}</span>"</span>
  route {
    <span class="hljs-attribute">destination_cidr_block</span> = <span class="hljs-string">"0.0.0.0/0"</span>
    gateway_id = <span class="hljs-string">"<span class="hljs-variable">${aws_internet_gateway.default_ig.id}</span>"</span>
  }
}
<span class="hljs-comment"># ...</span>
</code></pre><p>Then I would create our VPCs by reusing the module:</p>
<pre><code><span class="hljs-attribute">module</span> <span class="hljs-string">"us-east-1"</span> {
  <span class="hljs-attribute">source</span> = <span class="hljs-string">"../../modules/vpc"</span>
  aws_region = <span class="hljs-string">"us-east-1"</span>
  vpc_cidr_block = <span class="hljs-string">"10.0.0.0/16"</span>
}

module <span class="hljs-string">"us-east-2"</span> {
  <span class="hljs-attribute">source</span> = <span class="hljs-string">"../../modules/vpc"</span>
  aws_region = <span class="hljs-string">"us-east-2"</span>
  vpc_cidr_block = <span class="hljs-string">"10.1.0.0/16"</span>
}
<span class="hljs-comment"># ...</span>
</code></pre><p>However, for <code>us-east-1</code>, I needed to create a VPC peering between another VPC we have in this region. To make this work, beyond creating the peering connection, I need to modify the route table, adding an entry that will properly route the traffic.</p>
<pre><code><span class="hljs-comment"># ...</span>
<span class="hljs-attribute">resource</span> <span class="hljs-string">"aws_route"</span> <span class="hljs-string">"peer_vpc"</span> {
  <span class="hljs-attribute">route_table_id</span> = <span class="hljs-string">"<span class="hljs-variable">${module.us-east-1.public_route_table_id}</span>"</span>
  destination_cidr_block = <span class="hljs-string">"172.16.0.0"</span>
  vpc_peering_connection_id = <span class="hljs-string">"pcx-xxxxxx"</span>
}
<span class="hljs-comment"># ...</span>
</code></pre><p>This should work, right?</p>
<p><img src="https://miro.medium.com/max/1104/1*jY2KkLVPiD_ERvUkJeIvSg.jpeg" alt="the-donald"></p>
<p>The behavior I observed when mixing both styles was that if the standalone resources didn’t exist, they would be created. However, once created, if I ran <code>terraform apply</code> again, they would be deleted. If I tried one more time, they would be created and so on…</p>
<p>The gotcha is that you can’t mix both. I had to dig through some Terraform issues on Github to learn that.</p>
<p>Fortunately, this is now clear in the documentation, as it’s stated for example in <code>aws_route_table</code> resource:</p>
<blockquote>
<p><strong>NOTE on Route Tables and Routes:</strong> Terraform currently provides both a standalone <a target='_blank' rel='noopener noreferrer'  href="https://www.terraform.io/docs/providers/aws/r/route.html">Route resource</a> and a Route Table resource with routes defined in-line. At this time you cannot use a Route Table with in-line routes in conjunction with any Route resources. Doing so will cause a conflict of rule settings and will overwrite rules.</p>
</blockquote>
<p>The solution was to extract the in-line route within the module to its own resource:</p>
<pre><code><span class="hljs-comment"># ...</span>
<span class="hljs-attribute">resource</span> <span class="hljs-string">"aws_route"</span> <span class="hljs-string">"internet"</span> {
  <span class="hljs-attribute">route_table_id</span> = <span class="hljs-string">"<span class="hljs-variable">${aws_route_table.public.id}</span>"</span>

  destination_cidr_block = <span class="hljs-string">"0.0.0.0/0"</span>
  gateway_id = <span class="hljs-string">"<span class="hljs-variable">${aws_internet_gateway.default_ig.id}</span>"</span>
}
<span class="hljs-comment"># ..</span>
</code></pre><p>This way I can amend the route table outside the module without pulling my hair out.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>It has been great to work with Terraform. Sometimes I think it would feel better to burn alive, but still great.</p>
<p>From the time I started until now, I noticed improvements in the documentation, which might help to make the learning curve less steep.</p>
<p>Since I’m still learning, I probably got some things wrong. So there might be room to improve this article in the future as I discover new/better patterns.</p>
<hr>
<p>Did you like what you just read? Why don’t you buy me a beer with <a target='_blank' rel='noopener noreferrer'  href="https://tippin.me/@hbarcelos909">tippin.me</a>?</p>
<hr>
<p><img src="https://miro.medium.com/max/1058/1*L8XWsP_NT-yq4n63KncGEg.png" alt="that&#39;s-not-all-folks"></p>
]]></content:encoded></item><item><title><![CDATA[How I learnt to love and hate Terraform in the past few weeks]]></title><description><![CDATA[How I learnt to love and hate Terraform in the past few weeks
The tale of a joy/pain-ful lonely journey into the infrastructure-as-code world
Once upon a time…
I might have said this a couple of times, but the best and the worst part of working in a ...]]></description><link>https://blog.hbarcelos.dev/how-i-learnt-to-love-and-hate-terraform-in-the-past-few-weeks</link><guid isPermaLink="true">https://blog.hbarcelos.dev/how-i-learnt-to-love-and-hate-terraform-in-the-past-few-weeks</guid><category><![CDATA[Terraform]]></category><category><![CDATA[Story]]></category><category><![CDATA[learning]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Henrique Barcelos]]></dc:creator><pubDate>Thu, 03 Aug 2017 12:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1590599692103/sZFacIWbg.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="how-i-learnt-to-love-and-hate-terraform-in-the-past-few-weeks">How I learnt to love and hate Terraform in the past few weeks</h1>
<h2 id="the-tale-of-a-joy-pain-ful-lonely-journey-into-the-infrastructure-as-code-world">The tale of a joy/pain-ful lonely journey into the infrastructure-as-code world</h2>
<p>Once upon a time…</p>
<p>I might have said this a couple of times, but the best and the worst part of working in a startup environment is the diversity of activities you enroll in. One day you are debugging some bug in a Angular/React front-end page, the other you are pulling your hair off with some obscure infrastructure problem.</p>
<p>That is good because you sort of get an intensive crash course in many disciplines, being forced to learn things very fast — like front-end, back-end, infrastructure, design, business, billing, you name it — that would take you a lot of time (if at all) in a mid-size/large company. On the other hand, having to deal with such broad aspects together (sometimes, simultaneously more than one), means that you will not be a specialist on any of those subjects and the constant context-switching may drive your productivity low.</p>
<p>At Revmob, we are currently on an effort to strike a balance between flexibility and specialization in the tech team, with well defined squads and responsibilities. However, we still do not have an ops team, neither someone whose primary focus is developing and maintaining infrastructure, so basically everybody is responsible for it.</p>
<p>But, as we say here in Brazil, literally translated, “a dog with two owners starves to death” (I believe the closest saying in English would be “too many cooks spoil the broth”). With the lack of standards and, at some extent, of knowledge, whenever there was a problem with our infrastructure, we used to go full panic mode, basically dropping and recreating everything.</p>
<p><img src="https://miro.medium.com/max/1000/1*l8pcoURZhlFIkXdPuhc8jg.jpeg" alt="infra-dog"></p>
<blockquote>
<p>This is the poor infra-dog :(</p>
</blockquote>
<p>We have relied on AWS Elastic Beanstalk for a while and, whilst it reduces the effort to setup a basic web or worker-based standard infrastructure, once you need more flexibility, you end up struggling against the multiple levels of abstraction, resources that are hard to find, the complexity of CloudFormation stacks, configuration files using the <code>.ebextensions</code> directory inside projects leading to lots of code duplication, etc.</p>
<p>However, our main pain point was that the warm up time for a new instance being too high (about 7~8 minutes) and we verified that most of that time was spent on Beanstalk setup than on our applications. We managed to make some tweaks, by creating a custom AMI based on some Beanstalk-ready default images, so the warm up time went down to about 4~5 minutes. Still, this was not good enough for our highly elastic environments (oh, the irony!), as certain unpredictable high traffic spikes (very frequent in mobile advertising networks) could not be properly handled.</p>
<p>We needed to find a way around this problem.</p>
<p>Beyond that, we were facing the need for our first multi-region application. Since this would require a lot of work to setup by hand, we felt motivated to explore alternatives that could solve both problems.</p>
<p>I had heard about Terraform a couple of times and our CTO told us that the ops team in his previous company used it to manage infrastructure. So we decided to give it a shot.</p>
<p><em>If you are interested in a more technical article, check this out:</em></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://blog.henriquebarcelos.dev/things-i-wish-i-knew-about-terraform-before-jumping-into-it-43ee92a9dd65" data-card-controls="0" data-card-theme="light">https://blog.henriquebarcelos.dev/things-i-wish-i-knew-about-terraform-before-jumping-into-it-43ee92a9dd65</a></div>
<h1 id="then-the-journey-begins-">Then the journey begins…</h1>
<p>I was chosen/volunteered to be in charge of exploring this brave new world. The initial idea was that I would spend one sprint (which means 1 week for us) in order to make the setup needed to run two new applications, including the dreaded multi-region one.</p>
<p>We thought it would be hard to do pairing since none of us had the slightest idea of how that thing worked, so it was decided that I by myself would be my own team for that sprint.</p>
<p><img src="https://miro.medium.com/max/552/1*O85_WdC6j8qqF8LREBFIYw.jpeg" alt="teddy-bear-on-mud-pound-facing-road"></p>
<p>I started by doing the good ol’ <a target='_blank' rel='noopener noreferrer'  href="https://www.terraform.io/intro/getting-started/install.html">hello world</a>. The installation is quite easy, just download a binary file and put it in your <code>PATH</code> . Then I followed all the steps of the guide. Piece of cake.</p>
<blockquote>
<p>Now let’s get to the real work!</p>
</blockquote>
<hr>
<h1 id="troubles-in-paradise">Troubles in paradise</h1>
<p>I found a nice article series about Terraform, written by some company, other than HashiCorp — the creator of the whole thing — and started to read it. While it gave me some cool insights, when I started to try the code out, it simply did not work. I tried to move forward with the series, but ended up stumbling on the same problem.</p>
<p>I got kind of pissed and completely removed it from my bookmarks and never looked back. Later I found out that those articles were based on a previous version of Terraform, which was not compatible with the most recent I was using.</p>
<p>Then I decided to dive into the official documentation and continue by trial and error. That kind of worked for a while, however I was going in baby steps.</p>
<p>Differently from software development, where you can easily setup an automated test suite, put them to run on watch mode and get instant feedback when you make any changes, working with Terraform seemed to be some orders of magnitude slower. Many times I caught myself just staring at my screen to see what was going to happen after I changed a resource. But at least I felt I was moving forward.</p>
<p>At some point, however, I hit a wall. Then I went pray to our lord Google.</p>
<p><img src="https://miro.medium.com/max/1104/1*IoRC6yo1H83JZONHXo29iw.jpeg" alt="pray-to-google"></p>
<blockquote>
<p>Oh, mighty Google, please save this poor siner’s soul</p>
</blockquote>
<p>Nonetheless, the all-powerful was not very merciful at me. Each link of <em>“how to do X with Terraform”</em> led me to a completely different approach, which probably did not even work. Some articles, only a few months old, were already outdated (it seems like Terraform <code>0.9.x</code> was a completely new beast from <code>0.8.x</code>).</p>
<p>By leaps and bounds I could reach a somewhat satisfactory result for the simpler application I had. It was kind of nice to see all that code being translated into actual Load Balancers, Auto-Scaling Groups, VPCs, Security Groups, CloudWatch metrics and alarms, and so on.</p>
<p>It was already Friday an I was happy that I was done. Now I just needed to change some input parameters and Terraform would <em>automagically</em> reproduce the setup I had for the other application, then I would just need to make it multi-regional. Easy-peasy.</p>
<p>That’s what I thought.</p>
<p><img src="https://miro.medium.com/max/1104/1*SOp5W2mCJtdobTPM7ohdIw.jpeg" alt="willy-coyote-chasing-roadrunner-on-rocket"></p>
<blockquote>
<p>Yeah, not so fast, hasty!</p>
</blockquote>
<p>It turned out that Terraform does not work quite like I thought it would. I had gotten it all wrong. I had not understood that <em>modules</em> play a central role in Terraform code reuse patterns, so I just ignored them altogether. It was too late, the sprint was over and I had failed.</p>
<p>During the grooming meeting on the same Friday, I gave the status to my boss and he asked me if I wanted more time. I told him I would get everything done until the next Wednesday (yeah, I was that stupid). He agreed to allow me to continue my quest during the following week.</p>
<p>Also, in the meantime, since we are only two in my squad so far, I sort of left my squad-mate alone for the sprint and would leave him by himself again. He got sick of waiting for my return and joined another squad for the next sprint.</p>
<p><img src="https://miro.medium.com/max/1000/1*N3mnUHaJ9Lyy7vj7vas8Rw.jpeg" alt="sad-spongebob"></p>
<blockquote>
<p>I feel you, SpongeBob</p>
</blockquote>
<hr>
<h1 id="taking-a-step-back">Taking a step back</h1>
<p>During the weekend I was reflecting on what went wrong and I concluded that I was in frenzy mode . I needed to take it slower, gain more muscle and agility in order to win the challenge.</p>
<p>I focused the first days on understanding the internals of Terraform and to review some infrastructure-related and AWS specific concepts. For example, I had to remember a few things of that Computer Networks classes I had never payed too much attention during college.</p>
<blockquote>
<p>I would probably never have to write a Route Table by hand nor partition a VPC into several subnets my entire life, I don’t need this bull$*!7.</p>
</blockquote>
<p>I realized that while I was complaining about Beanstalk, it had spoiled me a a lot. But it wasn’t long before I could find that information in a dusty corner in my mind.</p>
<p><img src="https://miro.medium.com/max/1104/1*fbylbBy4b9eWJBJrUlp-zA.jpeg" alt="manipulating-dusty-book"></p>
<blockquote>
<p>Oh, so that’s how I do to calculate the number of bits I need in my subnet mask?</p>
</blockquote>
<p>After those mental push ups, I was back on track. I had finally understood why I needed modules, how to partition Terraform state and how to use it remotely. My code started to get reusable.</p>
<p>Of course, the screen staring wasn’t entirely over, so obviously I couldn’t get everything done by Wednesday as I promised.</p>
<hr>
<h1 id="the-epic-final-battle">The epic final battle</h1>
<p>On Thursday morning I made a promise to myself.</p>
<blockquote>
<p>I won’t drag this $*!7 with me another sprint. I will finish it until tomorrow.</p>
</blockquote>
<p><img src="https://miro.medium.com/max/1092/1*TCjM0TcOGK7azUDCXaBiDg.jpeg" alt="challenge-accepted"></p>
<blockquote>
<p>Time to suit up!</p>
</blockquote>
<p>So it begun. I was back on frenzy mode, but this time a little more conscious. My strategy was to deploy both two applications in a single region and, after making sure everything was running smoothly, I would replicate one of them across AWS 14 regions.</p>
<p>I was a little bit more savvy on Terraform than before, so my main struggle became the AWS quirks. One of the problems we had with Beanstalk finding specific resources through its console (have you tried to find the load balancer of an environment?), so I decided to use meaningful names for all resources I could.</p>
<p>Then came the name collisions. Luckily this was already solved by Terraform, allowing me to use name prefixes for certain resource type, generating a new name every time, while keeping its meaningfulness. I was also trying to use CodeDeploy, something we had never used before. This gave me a bit of work as well.</p>
<p>When I had figured out the problems with AWS, I felt the need for refactoring the infrastructure code to make it more manageable. I was ready to throw everything away and rebuild my infrastructure when I found out that Terraform allows you to <a target='_blank' rel='noopener noreferrer'  href="https://www.terraform.io/docs/commands/state/mv.html">move state</a>.</p>
<p><img src="https://miro.medium.com/max/960/1*JeyBix5DDFILbcpiXUWhTg.gif" alt="mind-blowing"></p>
<p>That moment was the first time I loved Terraform…</p>
<p>This feature saved me some time, as I could keep what I had created and just move the state around. I was getting close.</p>
<p>Before the setup was 100% functional for a single region, I got into a fight with <a target='_blank' rel='noopener noreferrer'  href="http://pm2.keymetrics.io/">PM2</a> as well (I’m a bit feisty, you might be thinking). Again it was something I had never used and, as it was late in the night on a Friday, the tiredness started to consume me. PM2 was able to throw a few strong punches against me.</p>
<p><img src="https://miro.medium.com/max/1104/1*FIHttGT4MnOz7OTZW7MCFg.jpeg" alt="rocky-balboa-taking-a-beat"></p>
<blockquote>
<p>PM2 looked like Apollo Creed…</p>
</blockquote>
<p>But, as Rocky Balboa himself, I managed to defeat the dreaded adversary and was ready to move on to the next challenge.</p>
<blockquote>
<p>Time to go multi-regional!</p>
</blockquote>
<p>It was about 5 a.m. of Saturday when I finished the first multi-regional setup. Sleep was a long lost. I wanted to finish everything before going home. I had to make some minor tweaks and after a few minutes — <em>bam</em> — there was my first multi-regional application deployed and ready to go.</p>
<p>Just to be sure, I ran a little test suite on Postman I had setup during development. The first time it failed for Japan, but I realized that the infrastructure was not fully set yet. One more try and everything ran smoothly \o/. It was 5:53 a.m.</p>
<p><img src="https://miro.medium.com/max/1104/1*6XI1_W51xnBNG9DCjX3qbw.jpeg" alt="freddy-mercury-with-one-arm-up"></p>
<p>At that moment, I started to loudly play “We are the Champions” at the Office.</p>
<p>Of course, there was no one there to listen… So I put if off, asked for my Uber and went home to get the sleep of the righteous.</p>
<hr>
<h1 id="what-would-i-do-different-">What would I do different?</h1>
<p>I’ve made several mistakes during those two weeks. I probably could’ve stressed less, suffered less, slept better and drunk more water during the process.</p>
<p>I believe our assumption that would be better to make this a single person job was wrong. There are so many things involved, it’s easy to screw up without someone helping you. I would definitely suggest people who are starting with Terraform to do pairing.</p>
<p>Furthermore, it got pretty lonely. Those who know me will probably say that I’m not the most sociable person they know, so trust me. I barely spoke to my teammates for two weeks, even though I was sitting right next to them. I couldn’t ask for insights — since nobody knew squad about what I was doing — or even just complain about my fate.</p>
<p>I also think that I aimed too high. I had to learn/remember so many things at once: Terraform, CodeDeploy, PM2, Computer Networks, AWS quirks and how to setup a full-blown multi-regional application. It was just too much. I should’ve started small and make improvements with time.</p>
<hr>
<h1 id="the-end-">The end (?)</h1>
<p>That was my story. I tried to summarize it as much as possible, but failed miserably.</p>
<p>I will probably write another post from a more technical perspective within the next few days, sharing the little I learned from this endeavor. Keep posted.</p>
<hr>
<p>Did you like what you just read? Buy me a beer with <a target='_blank' rel='noopener noreferrer'  href="https://tippin.me/@hbarcelos90">tippin.me</a>.</p>
<hr>
<p><img src="https://miro.medium.com/max/1104/1*feHNoKa_E3NB7kBPDwgd4Q.png" alt="thats-all-foks"></p>
]]></content:encoded></item></channel></rss>