<?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>Forem: Martijn Versluis</title>
    <description>The latest articles on Forem by Martijn Versluis (@martijnversluis).</description>
    <link>https://forem.com/martijnversluis</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%2F190921%2F2808d7f3-6ceb-403c-9f9b-eefa61d5d98e.png</url>
      <title>Forem: Martijn Versluis</title>
      <link>https://forem.com/martijnversluis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/martijnversluis"/>
    <language>en</language>
    <item>
      <title>TIL about VCR</title>
      <dc:creator>Martijn Versluis</dc:creator>
      <pubDate>Mon, 04 Nov 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/til-about-vcr-5d32</link>
      <guid>https://forem.com/kabisasoftware/til-about-vcr-5d32</guid>
      <description>&lt;p&gt;A while ago I contributed a change to the well-known Ruby gem &lt;a href="https://github.com/vcr/vcr"&gt;VCR&lt;/a&gt;. You can read all about it in &lt;a href="https://github.com/vcr/vcr/pull/765"&gt;this pull request&lt;/a&gt;, but basically it allows you to prevent interactions from being recorded until your test passes.&lt;/p&gt;

&lt;p&gt;To be able to make this change, I obviously had to dive into (a portion of) the source code to understand &lt;em&gt;what&lt;/em&gt; I had to change. By reading the code I discovered some VCR features I did not know, so they might be new to you too. Here are six things you might not know about VCR.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. You can change the serialization
&lt;/h2&gt;

&lt;p&gt;VCR is well known for the YAML files it uses to persist cassettes. By default that isn't a bad format: it is readable and Ruby developers are generally familiar with it. But if you would want to change the serialization, you can set it in the configuration. That could come in handy, for example if you're exporting the cassettes to a separate system that does not support YAML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_cassette_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;serialize_with: :json&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The available serializers are: &lt;code&gt;:yaml&lt;/code&gt;, &lt;code&gt;:syck&lt;/code&gt; or &lt;code&gt;:psych&lt;/code&gt; for YAML format, &lt;code&gt;:json&lt;/code&gt; for JSON format (powered by MultiJson) or &lt;code&gt;:compressed&lt;/code&gt;, which adds compression to the &lt;code&gt;:yaml&lt;/code&gt; serializer. You can even create and use your own serializer. It should respond to &lt;code&gt;file_extension&lt;/code&gt;, &lt;code&gt;serialize&lt;/code&gt; and &lt;code&gt;deserialize&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;VcrXmlSerializer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;file_extension&lt;/span&gt;
    &lt;span class="s1"&gt;'xml'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Qyoku&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key_converter: :none&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Nori&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;parser: :nokogiri&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cassette_serializers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:xml&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;VcrXmlSerializer&lt;/span&gt;

&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_cassette_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;serialize_with: :xml&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  2. You can change the persistence
&lt;/h2&gt;

&lt;p&gt;By default VCR generates files, which works fine for most use cases. You can implement your own storage though, for example to write all interactions to a database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;VcrDatabasePersister&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"INSERT INTO http_interactions (name, content) VALUES (?, ?)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_first_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT content FROM http_interactions WHERE name = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&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="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;db&lt;/span&gt;
    &lt;span class="no"&gt;SQLite3&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cassette_persisters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:database&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;VcrDatabasePersister&lt;/span&gt;

&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_cassette_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;persist_with: :database&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  3. You can hook into events
&lt;/h2&gt;

&lt;p&gt;VCR supports a number of hooks that allows you to change data before it is used or persisted by VCR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;before_record&lt;/code&gt;: change the HTTP interaction before it is recorded&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;before_playback&lt;/code&gt;: change a cassette before it is registered for use&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;before_http_request&lt;/code&gt;, &lt;code&gt;after_http_request&lt;/code&gt; and &lt;code&gt;around_http_request&lt;/code&gt; to perform an action before and/or after a HTTP request is performed (either stubbed or real requests).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you can wrap a timeout around each request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;around_http_request&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See: &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/hooks"&gt;https://relishapp.com/vcr/vcr/v/5-0-0/docs/hooks&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. You can use placeholders
&lt;/h2&gt;

&lt;p&gt;You can use the config option &lt;code&gt;define_cassette_placeholder&lt;/code&gt;, aliased as &lt;code&gt;filter_sensitive_data&lt;/code&gt; to replace data before it is written to a file. You can, for example, use it to filter passwords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VCR.configure do |config|
  config.filter_sensitive_data('&amp;lt;MASKED&amp;gt;') { ENV['API_PASSWORD'] }
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See: &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/configuration/filter-sensitive-data0"&gt;https://relishapp.com/vcr/vcr/v/5-0-0/docs/configuration/filter-sensitive-data&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. You can use cassette data inside your test
&lt;/h2&gt;

&lt;p&gt;One use case is when a HTTP request is time sensitive (eg. when signing a request). When using &lt;code&gt;use_cassette&lt;/code&gt; the cassette object is passed to the block, allowing to grab the recording time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;cassette&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_api_request&lt;/span&gt;
  &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cassette&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;originally_recorded_at&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'some secret key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See: &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/cassettes/freezing-time"&gt;https://relishapp.com/vcr/vcr/v/5-0-0/docs/cassettes/freezing-time&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Besides the recording time, the cassette exposes more metadata like the name, configuration and the file path. For more on the public API of &lt;code&gt;Cassette&lt;/code&gt; see: &lt;a href="https://rubydoc.info/gems/vcr/VCR/Cassette"&gt;https://rubydoc.info/gems/vcr/VCR/Cassette&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. You can use ERB in cassettes
&lt;/h2&gt;

&lt;p&gt;Yes, you can use ERB in a cassette. There are probably not many cases where you &lt;em&gt;should&lt;/em&gt; do it, but here is an example just to show what is possible. We use ERB to inject an API key in a cassette:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;http_interactions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
    &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://example.com/time?api_key=&amp;lt;%= api_key %&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UTF-8&lt;/span&gt;
      &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OK&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;application/json;charset=UTF-8&lt;/span&gt;
      &lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;47'&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UTF-8&lt;/span&gt;
      &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;2019-08-08T20:08:25.604188+02:00&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;
    &lt;span class="na"&gt;http_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.1'&lt;/span&gt;
  &lt;span class="na"&gt;recorded_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tue, 01 Nov 2011 04:58:44 GMT&lt;/span&gt;
&lt;span class="na"&gt;recorded_with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VCR 2.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;erb: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;api_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'TIME_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/time?api_key=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'TIME_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Response: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See: &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/cassettes/dynamic-erb-cassettes"&gt;https://relishapp.com/vcr/vcr/v/5-0-0/docs/cassettes/dynamic-erb-cassettes&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's all, folks!
&lt;/h2&gt;

&lt;p&gt;Hopefully these tips were useful to make testing your application easier. Do you need more help? At Kabisa we know a lot about writing good tests. &lt;a href="https://www.kabisa.nl/contact/"&gt;Leave us a message&lt;/a&gt; if you would like to get in touch.&lt;/p&gt;

</description>
      <category>vcr</category>
    </item>
    <item>
      <title>Matching multipart request bodies with VCR</title>
      <dc:creator>Martijn Versluis</dc:creator>
      <pubDate>Tue, 20 Aug 2019 13:35:23 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/matching-multipart-request-bodies-with-vcr-2mf4</link>
      <guid>https://forem.com/kabisasoftware/matching-multipart-request-bodies-with-vcr-2mf4</guid>
      <description>&lt;p&gt;When using VCR to test a HTTP multipart request, you might experience mismatching cassettes because of multipart boundaries. Multipart boundaries are most often randomly generated by the HTTP library or browser, so when VCR tries to match a multipart request with a cassette, the boundaries will be different and the match will fail.&lt;/p&gt;

&lt;p&gt;However, it is quite simple to replace the random boundaries by fixed ones before matching requests. A multipart request will have a &lt;code&gt;Content-Type&lt;/code&gt; header, which not only makes the request recognizable as multipart request but also specifies the exact boundary that is used. In this case I took a cassette from a Ruby project, so the &lt;code&gt;Content-Type&lt;/code&gt; looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multipart/form-data; boundary=----RubyFormBoundaryTsqIBL0iujC5POpr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Given this header we should be able to simply replace the boundaries with a fixed value. VCR has a configuration option called &lt;code&gt;match_request_on&lt;/code&gt;, which is an array that accepts symbols for predefined request matchers (see &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching"&gt;https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching&lt;/a&gt;) or a callable (Proc, Lambda). I'm not a real fan of using procs for this but as long as we create an object that responds to &lt;code&gt;call&lt;/code&gt; we will be fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VCRMultipartBodyMatcher&lt;/span&gt;
  &lt;span class="no"&gt;MULTIPART_HEADER_MATCHER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;%r{^multipart/form-data; boundary=(.+)$}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;normalized_multipart_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;normalized_multipart_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalized_multipart_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;multipart_request?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MULTIPART_HEADER_MATCHER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&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="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'----RubyFormBoundaryTsqIBL0iujC5POpr'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multipart_request?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;

    &lt;span class="no"&gt;MULTIPART_HEADER_MATCHER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can tell VCR to use our custom matcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_cassette_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;match_requests_on: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;VCRMultipartBodyMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when running the test the boundaries of the request and the cassette will be equal, so the cassette will only mismatch if the request parameters differ. 👍&lt;/p&gt;

&lt;p&gt;Do you need help with testing your application? At Kabisa we know a lot about writing good tests. &lt;a href="https://www.kabisa.nl/contact/"&gt;Leave us a message&lt;/a&gt; if you would like to get in touch.&lt;/p&gt;

</description>
      <category>vcr</category>
      <category>http</category>
      <category>multipart</category>
      <category>kabisa</category>
    </item>
    <item>
      <title>Matching multipart request bodies with VCR</title>
      <dc:creator>Martijn Versluis</dc:creator>
      <pubDate>Tue, 20 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/matching-multipart-request-bodies-with-vcr-32fa</link>
      <guid>https://forem.com/kabisasoftware/matching-multipart-request-bodies-with-vcr-32fa</guid>
      <description>&lt;p&gt;When using VCR to test a HTTP multipart request, you might experience mismatching cassettes because of multipart boundaries. Multipart boundaries are most often randomly generated by the HTTP library or browser, so when VCR tries to match a multipart request with a cassette, the boundaries will be different and the match will fail.&lt;/p&gt;

&lt;p&gt;However, it is quite simple to replace the random boundaries by fixed ones before matching requests. A multipart request will have a &lt;code&gt;Content-Type&lt;/code&gt; header, which not only makes the request recognizable as multipart request but also specifies the exact boundary that is used. In this case I took a cassette from a Ruby project, so the &lt;code&gt;Content-Type&lt;/code&gt; looks like this:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multipart/form-data; boundary=----RubyFormBoundaryTsqIBL0iujC5POpr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Given this header we should be able to simply replace the boundaries with a fixed value. VCR has a configuration option called &lt;code&gt;match_request_on&lt;/code&gt;, which is an array that accepts symbols for predefined request matchers (see &lt;a href="https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching"&gt;https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching&lt;/a&gt;) or a callable (Proc, Lambda). I'm not a real fan of using procs for this but as long as we create an object that responds to &lt;code&gt;call&lt;/code&gt; we will be fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VCRMultipartBodyMatcher&lt;/span&gt;
  &lt;span class="no"&gt;MULTIPART_HEADER_MATCHER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;%r{^multipart/form-data; boundary=(.+)$}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;normalized_multipart_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;normalized_multipart_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalized_multipart_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;multipart_request?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MULTIPART_HEADER_MATCHER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&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="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'----RubyFormBoundaryTsqIBL0iujC5POpr'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multipart_request?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;

    &lt;span class="no"&gt;MULTIPART_HEADER_MATCHER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we can tell VCR to use our custom matcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_cassette_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;match_requests_on: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;VCRMultipartBodyMatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, when running the test the boundaries of the request and the cassette will be equal, so the cassette will only mismatch if the request parameters differ. 👍&lt;/p&gt;

&lt;p&gt;Do you need help with testing your application? At Kabisa we know a lot about writing good tests. &lt;a href="https://www.kabisa.nl/contact/"&gt;Leave us a message&lt;/a&gt; if you would like to get in touch.&lt;/p&gt;

</description>
      <category>vcr</category>
    </item>
    <item>
      <title>How to handle JavaScript/frontend errors with Selenium Webdriver</title>
      <dc:creator>Martijn Versluis</dc:creator>
      <pubDate>Wed, 24 Apr 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/how-to-handle-javascript-frontend-errors-with-selenium-webdriver-3f2h</link>
      <guid>https://forem.com/kabisasoftware/how-to-handle-javascript-frontend-errors-with-selenium-webdriver-3f2h</guid>
      <description>&lt;p&gt;Dotfiles are text files that live in your &lt;code&gt;$HOME&lt;/code&gt; directory and help to configure your terminal and programs. When you are moving to a new computer, having dotfiles in a git repository makes copying these configurations a breeze.&lt;/p&gt;

&lt;p&gt;There are quite &lt;a href="https://github.com/search?q=dotfile+manager"&gt;a lot dotfile managers written&lt;/a&gt; in various languages. Basically these dotfile managers create symlinks to the dotfiles in the home directory. &lt;/p&gt;

&lt;p&gt;But do we really need a dotfile "manager" to symlink some files?&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet GNU Stow 🔨
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt; is a symlink farm manager, that makes packages of software appear in different locations on the filesystem.&lt;/p&gt;

&lt;p&gt;We can install the software with &lt;a href="https://brew.sh/"&gt;brew&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew install stow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Making yourself at home 🏠
&lt;/h2&gt;

&lt;p&gt;Let's create a repository &lt;code&gt;dotfiles&lt;/code&gt; and create our first dotfile that will be linked to the home directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir ~/dotfiles
$ cd ~/dotfiles
$ git init .
$ echo "A simple dotfile configuration." &amp;gt; .mydotfile
$ git add .mydotfile
$ git commit -m "Create dotfiles repository"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we have a repository, you can easily transfer. Next we need to symlink mydotfile into the home directory.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ~
$ ln -s ~/dotfiles/mydotfile .mydotfile
$ ls -la | grep mydotfile
lrwxr-xr-x    1 e  ff      20B 29 May 11:11 .mydotfile -&amp;gt; dotfiles/.mydotfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we have symlinked the dotfile in the repository to our home directory. The symlink, which is a reference to the original file, can be edited and viewed. However when you delete the symlink, the original file remains as is.&lt;/p&gt;

&lt;p&gt;We can automate the process of manually symlinking the files with Stow. As an example, let's create the following structure to setup neovim configuration files:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tree -a ~/dotfiles
.
└── neovim
        └── .config/nvim
        └── .init.vim
    └── .vimrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With Stow you can link those files by simply running one command.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ~/dotfiles
$ stow neovim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When we look in our home directory we see the following.&lt;/p&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -la ~ | grep vimrc&lt;br&gt;
lrwxr-xr-x     1 e  ff    22B May 29 11:12 .vimrc -&amp;gt;  dotfiles/neovim/.vimrc

&lt;p&gt;$ ls -la ~/.config/nvim/&lt;br&gt;
lrwxr-xr-x   1 e  ff    43B May 29 11:36 init.vim -&amp;gt; ../../dotfiles/neovim/.config/nvim/init.vim&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Let's reflect on what happened 📝&lt;br&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We ran stow with our &lt;code&gt;neovim&lt;/code&gt; dotfiles directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stow finds the &lt;code&gt;.vimrc&lt;/code&gt; and the subdirectory &lt;code&gt;.config/nvim/&lt;/code&gt; with an &lt;code&gt;.init.vim&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stow creates a symbolic link one directory above the current location and also creates the structure in the home directory for &lt;code&gt;~/.config/nvim/&lt;/code&gt; and symlinks &lt;code&gt;.init.vim&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stow makes an assumption that you want to symlink the files one directory above the directory where you have executed the stow command. Having the git repository in &lt;code&gt;~/dotfiles&lt;/code&gt; makes stow create the correct symlinks for us, in the &lt;code&gt;$HOME&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;In case you want to remove the symlink, you can type &lt;code&gt;stow -D neovim&lt;/code&gt;, which will unlink the files. Stow will warn you in case you are about to overwrite existing directories or files.&lt;/p&gt;

&lt;p&gt;I am using the described technique to symlink &lt;a href="https://github.com/thnukid/dotfiles"&gt;my own dotfiles&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotfiles</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>seleniumwebdriver</category>
    </item>
  </channel>
</rss>
