<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bartek Wilczek</title>
    <description>The latest articles on DEV Community by Bartek Wilczek (@bwilczek).</description>
    <link>https://dev.to/bwilczek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2046265%2Ffa706175-8b94-43f9-bef0-a0184232ebb8.jpg</url>
      <title>DEV Community: Bartek Wilczek</title>
      <link>https://dev.to/bwilczek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bwilczek"/>
    <language>en</language>
    <item>
      <title>Stubbing HTTP communication in E2E test with Hoverfly</title>
      <dc:creator>Bartek Wilczek</dc:creator>
      <pubDate>Wed, 06 Nov 2024 14:01:07 +0000</pubDate>
      <link>https://dev.to/bwilczek/stubbing-http-communication-in-e2e-test-with-hoverfly-31fn</link>
      <guid>https://dev.to/bwilczek/stubbing-http-communication-in-e2e-test-with-hoverfly-31fn</guid>
      <description>&lt;p&gt;Stubbing out communication with external services is a common practice in automated testing. It brings various advantages, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reduced flakiness&lt;/li&gt;
&lt;li&gt;faster execution&lt;/li&gt;
&lt;li&gt;ability to test edge cases (network errors)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is pretty easy to stub requests to remote services in unit tests, where the test runner process is executing the code under test. There's a variety of tools that hook into the HTTP client libraries and alter their behavior in runtime, making them an important tool in the tester's tool belt. Ruby, Java, Python, Node, and other have their &lt;code&gt;VCR&lt;/code&gt;, &lt;code&gt;sinon&lt;/code&gt;, &lt;code&gt;puffing-billy&lt;/code&gt;, &lt;code&gt;Betamax&lt;/code&gt;, &lt;code&gt;Talkback&lt;/code&gt;. None of these tools however has been designed to provide a technology agnostic solution for complete distributed app stack.&lt;/p&gt;

&lt;p&gt;For E2E testing communication stubbing is not simple because the test runner lives in a different process then the application under test. And this makes changing the behavior of the running app in runtime impossible. Or at least very, very hacky and standing in the way of E2E testing paradigm.&lt;/p&gt;

&lt;p&gt;Additionally the application under test can be multi-process itself: consider clustered HTTP server and a separate service for processing of background jobs. In order to provide consistent behavior of the whole stack all processes should be experiencing the same responses for the same requests. Tools listed in previous paragraph won't help in this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hoverfly enters the stage
&lt;/h2&gt;

&lt;p&gt;The solution to this problem is making all the processes in the stack route their HTTP communication through a proxy server and let this proxy perform any manipulation on the responses, as required by the test scenario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.hoverfly.io/en/latest/" rel="noopener noreferrer"&gt;Hoverfly&lt;/a&gt; is (almost) ideal tool for this purpose. It hooks itself into the HTTP requests processing seamlessly, without needing to alter the applications under test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Settings things up
&lt;/h2&gt;

&lt;p&gt;All applications involved in E2E test scenarios have to be configured to use an HTTP proxy server for external request they make. Setting this up can vary, depending on the libraries used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting Hoverfly
&lt;/h3&gt;

&lt;p&gt;But first things first. Let's start a local instance of Hoverfly, that the other services will use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; hoverfly &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8888:8888 &lt;span class="nt"&gt;-p&lt;/span&gt; 8500:8500 spectolabs/hoverfly:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important to know where services under test run. If they run in docker containers Hoverfly container should be created in the same network, and referred by name &lt;code&gt;hoverfly&lt;/code&gt;. If the services run on the host (locally) the correct address for Hoverfly service would be &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As one can see two ports are being exposed: &lt;code&gt;8500&lt;/code&gt; for the actual proxy and &lt;code&gt;8888&lt;/code&gt; for the admin interface and the REST API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring proxy with env variables
&lt;/h3&gt;

&lt;p&gt;In ideal scenario the desired configuration should be achieved without needing to change a single line of application's code, using only environment variables. There's an (informal) standard for proxy configuration, that involves three variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http_proxy&lt;/code&gt; - holds proxy URL to be used for HTTP requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https_proxy&lt;/code&gt; - holds proxy URL to be used for HTTPS requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no_proxy&lt;/code&gt; - holds a coma separated list of URLs that should not go through a proxy. Typically &lt;code&gt;localhost&lt;/code&gt; or other services from the same app stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As stated before our instance of Hoverfly runs at &lt;code&gt;http://hoverfly:8500&lt;/code&gt;. Then a typical set of variables for E2E test stack would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;http_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://hoverfly:8500
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;https_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://hoverfly:8500
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;no_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost,127.0.0.1,hoverfly,db,web,search,other,services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most likely these would be declared in GitHub Action workflow, or &lt;code&gt;docker-compose.yml&lt;/code&gt;, or &lt;code&gt;kubernetes&lt;/code&gt; manifests, or wherever the stack is started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring proxy per library
&lt;/h3&gt;

&lt;p&gt;Some HTTP client libraries do not respect these env vars. If this is the case the configuration will have to be provided in the code. For example &lt;code&gt;node-fetch&lt;/code&gt; requires some tweaking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HttpsProxyAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https-proxy-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsProxyAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPS_PROXY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;etc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately, the changes are minimal and (assuming that apps under test are properly designed) should happen in just one place in the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring SSL certificate
&lt;/h3&gt;

&lt;p&gt;In order to support requests to HTTPS sites Hoverfly comes with a bundled &lt;a href="https://github.com/SpectoLabs/hoverfly/blob/master/core/cert.pem" rel="noopener noreferrer"&gt;SSL certificate&lt;/a&gt;. However, as this is a testing tool, this certificate is self-signed, and needs to be marked as "trusted" before the applications using the proxy will attempt to connect to secure sites.&lt;/p&gt;

&lt;p&gt;Similarly as with proxy URL this goal can be achieved in a way transparent to the apps' code, by trusting the certificate on a global (OS) level. On &lt;code&gt;debian&lt;/code&gt; systems it can be done with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;cert.pem /usr/local/share/ca-certificates/hoverfly.crt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not all apps however respect this approach. If this is the case with your HTTP client please refer to its documentation to find a proper solution. It shouldn't be too hard though, as often the issue can be resolved with an environment variable. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python's &lt;code&gt;requests&lt;/code&gt; package: &lt;code&gt;REQUESTS_CA_BUNDLE=cert.pem&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Node's extra CA setting: &lt;code&gt;NODE_EXTRA_CA_CERTS=cert.pem&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Curl's extra CA setting: &lt;code&gt;CURL_CA_BUNDLE=cert.pem&lt;/code&gt; (it should respect system settings though)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example workflow
&lt;/h2&gt;

&lt;p&gt;Once the applications are configured to use Hoverfly proxy the actual testing workflow can start. Before the tests can be executed the responses have to be prepared. In Hoverfly terms a collection of request/response pairs is called a &lt;a href="https://docs.hoverfly.io/en/latest/pages/keyconcepts/simulations/simulations.html" rel="noopener noreferrer"&gt;Simulation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are two ways of creating simulations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recording a simulation
&lt;/h3&gt;

&lt;p&gt;Hoverfly can work in several &lt;a href="https://docs.hoverfly.io/en/latest/pages/keyconcepts/modes/modes.html" rel="noopener noreferrer"&gt;modes&lt;/a&gt;. For this article we care only about &lt;code&gt;capture&lt;/code&gt; and &lt;code&gt;simulate&lt;/code&gt; modes. First for recording real traffic, second for serving the pre-recorded responses, not allowing any connections to real systems.&lt;/p&gt;

&lt;p&gt;The are several ways to set the mode to the required value. First is using the admin panel at &lt;code&gt;http://localhost:8888&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another way is using CURL and REST API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "capture"}'&lt;/span&gt; http://localhost:8888/api/v2/hoverfly/mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last way is calling the REST API from the code. The code examples in this article use &lt;code&gt;TypeScript&lt;/code&gt; and &lt;a href="https://www.npmjs.com/package/@bwilczek/hoverfly-client" rel="noopener noreferrer"&gt;@bwilczek/hoverfly-client&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@bwilczek/hoverfly-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8888&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;capture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the mode has been set Hoverfly is ready to record the traffic going through it. It's time to let the apps under test perform their requests and let Hoverfly record them. Typically app user (or acceptance tester, so most like you, dear reader) will just one the app UI and click through the functionality in question.&lt;/p&gt;

&lt;p&gt;When the scenario is completed it's time to save the recorded traffic to a file, so that it could be reused in the future. This can be achieved it two ways.&lt;/p&gt;

&lt;p&gt;Using CURL and REST API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8888/api/v2/simulation &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; simulation.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from &lt;code&gt;TypeScript&lt;/code&gt; code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSimulation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;saveSimulationToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simulation.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The saved &lt;code&gt;simulation.json&lt;/code&gt; file can now be edited in any text editor to remove any redundant data, simplify response body, or provide responses for specific edge case scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crafting a simulation in the code
&lt;/h2&gt;

&lt;p&gt;Another approach is crafting the simulation programmatically, in the code. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;buildSimulation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ResponseData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RequestMatcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;saveSimulationToFile&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@bwilczek/hoverfly-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResponseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"result": "error", "message": "Insufficient balance"}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;encodedBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestMatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/invoices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment.provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildSimulation&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;saveSimulationToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simulation.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Serving responses from a simulation
&lt;/h3&gt;

&lt;p&gt;Once the traffic is recorded in a simulation file it's time to change Hoverfly mode to &lt;code&gt;simulate&lt;/code&gt; and start using the pre-recorded responses instead of real services.&lt;/p&gt;

&lt;p&gt;Changing mode is easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# curl:
curl -X PUT -H "Content-Type: application/json" -d '{"mode": "capture"}' http://localhost:8888/api/v2/hoverfly/mode

# TypeScript:
await client.setMode({mode: 'simulate'})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uploading the simulation JSON isn't hard as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# curl:
curl -X PUT -H "Content-Type: application/json" -d @simulation.json http://localhost:8888/api/v2/simulation

# TypeScript:
await client.uploadSimulation(buildSimulationFromFile('simulation.json'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously simulations crafted by hand can be uploaded without having to be saved to a JSON file first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildSimulation&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadSimulation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware and Journal
&lt;/h3&gt;

&lt;p&gt;Hoverfly comes with two more handy tools that can provide more flexibility to the test framework. First one is &lt;a href="https://docs.hoverfly.io/en/latest/pages/keyconcepts/middleware.html" rel="noopener noreferrer"&gt;middleware&lt;/a&gt;,&lt;br&gt;
a mechanism that can modify the responses dynamically, make requests to real systems, trigger callback webhooks or perform any logic that could be implemented inside a simple HTTP application. This topic is so broad and project specific that it goes far beyond the scope of this article. It's being mentioned here so that you know that if there's something more sophisticated that your stubbed communications needs to do, middleware can help.&lt;/p&gt;

&lt;p&gt;The other concept is Journal, which is basically a registry of performed HTTP requests that went through Hoverfly. It's essential for making assertions like &lt;em&gt;"Expect that payment provider has been queried for pending transactions"&lt;/em&gt;. More on this in the example below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example workflow with hoverfly-client
&lt;/h2&gt;

&lt;p&gt;Let's demonstrate the features of Hoverfly in an example &lt;code&gt;jest&lt;/code&gt; test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@jest/globals&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;buildSimulation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ResponseData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RequestMatcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;saveSimulationToFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@bwilczek/hoverfly-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://hoverfly:8888&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fetch invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;purgeSimulation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simulate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;purgeJournal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// upload some default requests/responses that should be always active, for example authenthication&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadSimulation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buildSimulationFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default_traffic.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sufficient balance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// append simulation for this scenario to the one already present in Hoverfly&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendSimulation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buildSimulationFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_sufficient_balance.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;// do some actions in the UI, that will make the app under test perform a request to payment provider&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submitPaymentButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// assert that the backend really performed a request to the payment provider&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentsJournal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchJournal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment.provider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]}})&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentsJournal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Stubbing HTTP communication in a distributed, multi-process system is not only possible, but it's also not that hard. HTTP proxy is the right tool for this purpose, and Hoverfly provides just the right features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexible request matchers&lt;/li&gt;
&lt;li&gt;Middleware for custom logic&lt;/li&gt;
&lt;li&gt;Support for SSL&lt;/li&gt;
&lt;li&gt;Journal for tracking of the processed requests&lt;/li&gt;
&lt;li&gt;JSON REST API for easy configuration&lt;/li&gt;
&lt;li&gt;Simulating latency and outages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As demonstrated in this article, introduction of Hoverfly to any app stack is easy. With minimal or no changes to the code it opens the apps for testing of use cases not achievable with requests to real external services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;As great as Hoverfly is it comes with a few caveats:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;destination&lt;/code&gt;, &lt;code&gt;no_proxy&lt;/code&gt; and direct requests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Switching Hoverfly on and off dynamically for certain URLs is hard. &lt;code&gt;no_proxy&lt;/code&gt; variable provides a static list, while &lt;a href="https://docs.hoverfly.io/en/latest/pages/keyconcepts/destinationfiltering.html" rel="noopener noreferrer"&gt;destination&lt;/a&gt; filtering relies on a whitelist regular expression. Implementation of a test suite that runs some scenarios against real payment provider and some against a simulated one&lt;br&gt;
is tricky and requires some fancy logic in a stateful middleware. It's doable though.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;not every lib respects &lt;code&gt;http_proxy&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As described above: some HTTP libraries won't respect the standard ENV variables and require changes to client initialization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;not every lib respects default location of SSL certificates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As described above: some HTTP libraries won't trust all system's certificates and require changes to client initialization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;default certificate does not work well with time traveling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scenarios that involve time traveling might not work as expected, as the SSL certificate shipped with Hoverfly has more "present" validity period. A custom certificate, valid +/- 30 years from now could be generated and used instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one, huge simulation, cannot upload/delete single pairs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;REST API operates (upload/download) on a whole simulation - not individual request/response pairs. Adding or subtracting responses requires some extra coding, fortunately it can be abstracted away in a client library, like the one used in the examples in this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON format of simulations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pretty often request or response payload is also in JSON format, what makes manual changes to the simulation files hard. JSON does not support line splitting and the JSON content is at easiest case escaped and stored in a very long line. In a harder case it can be &lt;code&gt;gzip&lt;/code&gt; or &lt;code&gt;brotli&lt;/code&gt; compressed and then serialized in &lt;code&gt;base64&lt;/code&gt;. Of course editing such payload is still possible, but a bit harder then if a different format was used.&lt;/p&gt;

&lt;h5&gt;
  
  
  Cover image created with ChatGPT
&lt;/h5&gt;

</description>
      <category>e2e</category>
      <category>testing</category>
      <category>hoverfly</category>
      <category>proxy</category>
    </item>
    <item>
      <title>Practical approach to software complexity</title>
      <dc:creator>Bartek Wilczek</dc:creator>
      <pubDate>Tue, 29 Oct 2024 08:46:29 +0000</pubDate>
      <link>https://dev.to/bwilczek/practical-approach-to-software-complexity-143b</link>
      <guid>https://dev.to/bwilczek/practical-approach-to-software-complexity-143b</guid>
      <description>&lt;p&gt;As software engineers we intuitively know what is software complexity and what impact it makes on the long term maintainability. (In case you didn’t know: the smaller the complexity the easier the code is to maintain).&lt;/p&gt;

&lt;p&gt;In long living projects complexity tends to grow over time, and that’s normal. It can be reduced (by certain amount) by refactoring, tooling upgrade or other kinds of redesign. Our (engineers') problem is that such initiatives do not bring immediate business value to the stakeholders. Therefore it could be hard to convince them that they should invest team’s effort into such projects.&lt;/p&gt;

&lt;p&gt;In this post I’d like to provide stakeholder-friendly definition of complexity and also explain why keeping it under control is good not only for engineers’ happiness, but also for business’s sustainability.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is complexity?
&lt;/h1&gt;

&lt;p&gt;Let’s use the image below to explain what complexity is.&lt;/p&gt;

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

&lt;p&gt;Complexity is a metric describing how difficult to change the software is.&lt;/p&gt;

&lt;p&gt;It consists of two components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;essential complexity&lt;/strong&gt; - originates from the business rules. The very core of the system. Cannot be reduced without sacrificing the functionality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;accidental complexity&lt;/strong&gt; - originates from the implementation. Brings no business value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smaller accidental complexity overhead the better.&lt;/p&gt;

&lt;p&gt;Now let’s go back to the picture above.&lt;/p&gt;

&lt;p&gt;Think of the clean wiring as the essential complexity with very little accidental complexity added. The system is not simple, because the domain is not simple. It looks easy to comprehend and change though, as the non-domain overhead is minimal.&lt;/p&gt;

&lt;p&gt;Now think of the messy wiring as the essential complexity with a lot of accidental complexity on top of it. It does the same functionality as the first one, but is much harder to comprehend and change.&lt;/p&gt;

&lt;p&gt;Whenever engineers say that certain initiative will reduce the complexity they mean removing the obsolete wires and simplifying the layout of the essential ones.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where does the accidental complexity come from?
&lt;/h1&gt;

&lt;p&gt;Accidental complexity can be added to the software in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;unintentionally&lt;/strong&gt;, when the engineering team does not have the experience with the
technical solutions required to complete certain task. This happens despite the best
efforts and good will displayed by the team. It can be mitigated by involving people with
more experience early in the solution design. Reaching out to architects should be a
good starting point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;intentionally&lt;/strong&gt;, when certain shortcuts are being made in order to achieve a short term
goal. Can be mitigated by being more flexible with deadlines. When in doubt refer to the
messy wiring picture and ask yourself whether adding a random, loose wire that can
make one project go live a bit earlier is beneficial for other projects in the long run.
Spoiler alert: it is not :)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How to measure the complexity?
&lt;/h1&gt;

&lt;p&gt;This usually depends on the programming language used in the project. Different languages and different tools use different formulas, but as long we we select one of them and stick to it we can track how the complexity changes over time.&lt;/p&gt;

&lt;p&gt;Taking actual measurements is pretty easy and usually limited to execution of a single command. In &lt;code&gt;ruby&lt;/code&gt; universe that would be &lt;a href="https://github.com/seattlerb/flog" rel="noopener noreferrer"&gt;flog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pseudocode for the calculation of an impact made on complexity by a given commit ABC could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# checkout commit prior to commit in question&lt;/span&gt;
git checkout ABC^

&lt;span class="c"&gt;# compute complexity for that commit&lt;/span&gt;
complexity_before &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;parse output from flog&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="c"&gt;# checkout the commit in question&lt;/span&gt;
git checkout ABC

&lt;span class="c"&gt;# compute complexity for it&lt;/span&gt;
complexity_after &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;parse output from flog&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="c"&gt;# calculate the impact&lt;/span&gt;
delta &lt;span class="o"&gt;=&lt;/span&gt; complexity_after - complexity_before
print &lt;span class="s2"&gt;"Commit ABC has changed overall complexity by #{delta}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To assess the impact made by the whole project simply run this formula for every commit associated with the project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why we should measure it?
&lt;/h1&gt;

&lt;p&gt;Because &lt;em&gt;you cannot manage what you cannot measure&lt;/em&gt;. Prior to giving green light to a potential refactoring project (with no direct business value associated) stakeholders would need a metric that could be used to assess the project impact. They will not be willing to invest resources into vague promises that &lt;em&gt;this project will make the code better&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;However after they have been familiarized with the notion that high complexity is bad for long-term maintainability they could be more eager on investing into a project that will e.g. &lt;em&gt;reduce the complexity by 3%&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After the project is approved and completed it is worth to assess the long term impact it made on other, more common metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team velocity/capacity&lt;/strong&gt; - it should increase as changing less complex code is easier, giving more time to implement new features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure cost&lt;/strong&gt; - it should decrease as less complex code requires less resources to run on production and CI environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering team satisfaction&lt;/strong&gt; - it should increase for two reasons:

&lt;ul&gt;
&lt;li&gt;engineers love making code better and letting them work on a refactoring project makes them happy&lt;/li&gt;
&lt;li&gt;engineers hate working with hacky, overcomplicated code. Making the code more elegant heals that pain&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Whenever technical projects (refactoring, tooling upgrades) face resistance from the stakeholders the metaphor of reducing complexity could convince them to reassess the priorities. It's like reducing the &lt;em&gt;fixed costs&lt;/em&gt;, or other &lt;em&gt;liabilities&lt;/em&gt;: frees up the &lt;em&gt;resources&lt;/em&gt; to be allocated on actual development. It's measurable, traceable, and easy to introduce to the development process.&lt;/p&gt;

</description>
      <category>complexity</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
