<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://remimercier.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://remimercier.com/" rel="alternate" type="text/html" /><updated>2026-04-03T14:30:29+00:00</updated><id>https://remimercier.com/feed.xml</id><title type="html">Rémi Mercier</title><subtitle>The blog of Rémi Mercier, Ruby on Rails Developer.</subtitle><author><name>Rémi Mercier</name></author><entry><title type="html">IndieRails Podcast Interview</title><link href="https://remimercier.com/indierails-pocast-interview/" rel="alternate" type="text/html" title="IndieRails Podcast Interview" /><published>2026-03-10T00:00:00+00:00</published><updated>2026-03-10T00:00:00+00:00</updated><id>https://remimercier.com/indie-rails</id><content type="html" xml:base="https://remimercier.com/indierails-pocast-interview/"><![CDATA[<p>I joined Jeremy Smith and Jess Brown on the <a href="https://www.indierails.com/" target="\_blank">IndieRails podcast</a> to talk about how I managed the transition from stained-glass master to <a href="/work/">freelance software engineer</a>.</p>

<iframe src="https://share.transistor.fm/e/418db5ad/?color=333333&amp;background=ffffff" style="height:180px;width:100%" frameborder="0" scrolling="no" seamless="true">
</iframe>

<p>Here are my notes:</p>

<h3 id="the-importance-of-a-well-crafted-pitch">The importance of a well-crafted pitch</h3>

<p>I already wrote about <a href="/from-stained-glass-master-to-software-developer/">the differences between my story and my history</a> – or how I had crafted a pitch to present my convoluted professional path – but it bears repeating the benefits of a pitch:</p>
<ul>
  <li>It allows the people you talk to to verify you’re not a complete fluke.</li>
  <li>It hooks your audience and gets out of the way quickly so you can focus on what makes you a great hire / freelancer.</li>
  <li>It filters out the clients/teams with which you’re not compatible: mindset, values, etc.</li>
</ul>

<p>The pitch came from a place of being tired of faking a competency in software engineering I did not have at the time (circa 2018). So, in a sense, it was a way to <a href="/own-your-story/">own my story</a> and to celebrate what I had learned in the previous 10 years of my professional life.</p>

<p>It’s also a great punchline – from stained-glass master to software engineer sticks in the brain and you become memorable.</p>

<h3 id="the-value-of-transferable-skills">The value of transferable skills</h3>

<p>My first work in tech – as part of a tech start-up marketing team – was due to my ability to convey complex topics to average people. Namely, I worked with an agricultural professional syndicate to explain the European Common Agricultural Policy through drawings and infographics. This specific experience, quite far from tech, got me through a door which later led to becoming a programmer.</p>

<p>Software engineering is a lot like this:</p>
<ul>
  <li>You take a complex set of problems that you need to intimately understand.</li>
  <li>You organise and explain them to shareholders / clients.</li>
  <li>You translate these into specifications then into code.</li>
</ul>

<p>My <a href="/what-is-an-api/">first piece of writing – tech wise – was about APIs</a>. I wrote it, while being a marketer, mostly because the CTO of my then-employer had started his explanation with these very words:</p>

<blockquote>
  <p>An API is a contract.</p>
</blockquote>

<p>An explanation that had left me more confused than ever.</p>

<p>Throughout all my various careers, I acquired a lot of skills I now transpose to building software.</p>

<h3 id="writing-is-learning">Writing is learning</h3>

<p>Ever since I started learning programming, I have written about my learnings. It’s scary to show the world what you (don’t) know – or a slice of that, to be honest – but it also shows an appreciation for learning and contributing to the community.</p>

<p>Most problems we tackle as software engineers are not that uncommon. So writing about our findings allows other developers to solve the same problems faster.</p>

<p>Using my website as a worry stone is a good way to convey the care I take in my practice. It helps me refine my craft, whether it takes shape as words, page layouts or illustrations. Writing is also a great way for me to properly store what I learn in my brain.</p>

<p>All in all, my writing has opened more doors than it has closed.</p>

<h3 id="reflecting-on-my-first-freelancing-gig">Reflecting on my first freelancing gig</h3>

<p>In a <a href="/reflecting-on-2025/">previous post</a>, I briefly touched on my first experience as a freelancer, some 15 years ago. Jess’ question about this part of my life was a great way to do a bit of a retrospective on it. What changed between then and now is mostly that I became an all-round better human. Sure, I added a few processes to my freelancing practice, but the game changer was simply growing and learning as a person. Less maturity issues.</p>

<p>We should learn to share more publicly the things that are wrong or imperfect. Serving clients is not always about being perfect, but about learning how to do it. Focus on building a good attitude on top of a good skill set, and you’ll love working for yourself. Shift the focus from the end goal to the daily practice.</p>

<p>We talked about so much more: finding support as a freelancer, decoupling value from time billed, trying to build a first small-scale product just for fun, etc.</p>

<p>Thank you so much to Jeremy and Jess for having me on the pod. It was a blast!</p>

<p>Cheers,</p>

<p>Rémi - <a href="https://ruby.social/@remi">@remi@ruby.social</a></p>]]></content><author><name>Rémi Mercier</name></author><category term="career" /><summary type="html"><![CDATA[I joined Jeremy Smith and Jess Brown on the IndieRails podcast to talk about how I managed the transition from stained-glass master to freelance software engineer.]]></summary></entry><entry><title type="html">Using Minitest::Spec in Rails? Watch out for the lifecycle hooks!</title><link href="https://remimercier.com/minitest-spec-and-rails-hooks/" rel="alternate" type="text/html" title="Using Minitest::Spec in Rails? Watch out for the lifecycle hooks!" /><published>2026-03-03T00:00:00+00:00</published><updated>2026-03-03T00:00:00+00:00</updated><id>https://remimercier.com/how-rails-integrates-minitest</id><content type="html" xml:base="https://remimercier.com/minitest-spec-and-rails-hooks/"><![CDATA[<p>After picking up <a href="https://remimercier.com/introduction-to-minitest/">Minitest</a> to complement the QA process of one of my retainer clients, I’ve had confusing errors in our test suite for a while. We were moving fast to cover critical parts of the application, so we never had the time to investigate the flakiness thoroughly.</p>

<p>While preparing my upcoming talk – Lost in Minitest, the missing guide for Minitest tourists – I dug into how Ruby on Rails pulls in Minitest. And there I found the answers to my burning question: why on Earth does the following test fail?</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s2">"test_helper"</span>

<span class="k">class</span> <span class="nc">CustomerIdentificationTest</span> <span class="o">&lt;</span> <span class="no">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</span>
  <span class="n">before</span> <span class="p">{</span> <span class="vi">@customer</span> <span class="o">=</span> <span class="n">customers</span><span class="p">.</span><span class="nf">buffy</span> <span class="p">}</span>
  
  <span class="n">it</span> <span class="s2">"checks proofs of identification"</span> <span class="k">do</span>
    <span class="n">get</span> <span class="n">customer_identification_path</span><span class="p">(</span><span class="vi">@customer</span><span class="p">)</span>
    
    <span class="n">assert_response</span> <span class="ss">:success</span>
  <span class="k">end</span>
  
  <span class="n">describe</span> <span class="s2">"when the customer has already been verified"</span> <span class="k">do</span>
    <span class="n">setup</span> <span class="p">{</span> <span class="vi">@customer</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">verified_at: </span><span class="mi">2</span><span class="p">.</span><span class="nf">days</span><span class="p">.</span><span class="nf">ago</span><span class="p">)</span> <span class="p">}</span>
    
    <span class="n">it</span> <span class="s2">"returns a :success response"</span> <span class="k">do</span>
      <span class="n">get</span> <span class="n">customer_identification_path</span><span class="p">(</span><span class="vi">@customer</span><span class="p">)</span>
    
      <span class="n">assert_response</span> <span class="ss">:success</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>These tests look pretty unremarkable:</p>
<ul>
  <li>The main context checks the response status for a new customer.</li>
  <li>The nested context checks the response for an existing customer.</li>
</ul>

<p>A few things worth noting:</p>
<ul>
  <li>I’m using the <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> syntax.</li>
  <li>We’re in a Ruby on Rails app (see the <code class="language-plaintext highlighter-rouge">&lt; ActionDispatch::IntegrationTest</code>).</li>
  <li>Several people are working on the test files.</li>
</ul>

<p>Wait, what? How did I infer this?</p>

<p>Notice the difference in how each test is set up? The first test uses the <code class="language-plaintext highlighter-rouge">before</code> block syntax that is part of <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>. The second test uses the <code class="language-plaintext highlighter-rouge">setup</code> block syntax, which is the Rails custom syntax. Minitest being pretty lax about conventions, two people used different syntaxes in the same file.</p>

<p>Is it problematic, though?</p>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">  <span class="nv">$ </span>bin/rails <span class="nb">test
  </span>Running via Spring preloader <span class="k">in </span>process 31584
  Run options: <span class="nt">--seed</span> 16480
  
  <span class="c"># Running:</span>
  
  .......................E
  
  Error:
  CustomerIdentificationTest::when the customer has already been verified#test_0001_returns a :success response:
  NoMethodError: undefined method <span class="s1">'update'</span> <span class="k">for </span>nil <span class="nb">test</span>/integration/customer_identification_test.rb:6:in <span class="s1">'block (2 levels) in &lt;class:CustomerIdentificationTest&gt;'</span></code></pre></figure>

<p>Well, looks like it is.</p>

<h2 id="a-detour-on-picking-a-standard">A detour on picking a standard</h2>

<p>I’m big on <a href="/pick-a-standard/">picking a standard and moving on</a>. But I also know that sometimes, you have to give yourself time to play with things before committing to a guideline.</p>

<p>It’s like living in a house: live in it for a while before you start knocking walls down.</p>

<p>Due to the lack of Minitest onboarding, we were careful not to draw rules early on. We wanted to feel where our setup was stretching at the seams. What was working for us and what was not.</p>

<p>Coding with feelings? Why not.</p>

<h2 id="back-to-our-failing-test">Back to our failing test</h2>

<p>Taking the time to feel our way through our setup allowed us to experiment with our tests. Here’s an updated version of the failing test, where I inverted the <code class="language-plaintext highlighter-rouge">setup</code> and <code class="language-plaintext highlighter-rouge">before</code> blocks.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s2">"test_helper"</span>

<span class="k">class</span> <span class="nc">CustomerIdentificationTest</span> <span class="o">&lt;</span> <span class="no">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</span>
  <span class="n">setup</span> <span class="p">{</span> <span class="vi">@customer</span> <span class="o">=</span> <span class="n">customers</span><span class="p">.</span><span class="nf">buffy</span> <span class="p">}</span>
  
  <span class="n">it</span> <span class="s2">"checks proofs of identification"</span> <span class="k">do</span>
    <span class="n">get</span> <span class="n">customer_identification_path</span><span class="p">(</span><span class="vi">@customer</span><span class="p">)</span>
    
    <span class="n">assert_response</span> <span class="ss">:success</span>
  <span class="k">end</span>
  
  <span class="n">describe</span> <span class="s2">"when the customer has already been verified"</span> <span class="k">do</span>
    <span class="n">before</span> <span class="p">{</span> <span class="vi">@customer</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">verified_at: </span><span class="mi">2</span><span class="p">.</span><span class="nf">days</span><span class="p">.</span><span class="nf">ago</span><span class="p">)</span> <span class="p">}</span>
    
    <span class="n">it</span> <span class="s2">"returns a :success response"</span> <span class="k">do</span>
      <span class="n">get</span> <span class="n">customer_identification_path</span><span class="p">(</span><span class="vi">@customer</span><span class="p">)</span>
    
      <span class="n">assert_response</span> <span class="ss">:success</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Can you see where this is going?</p>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">  <span class="nv">$ </span>bin/rails <span class="nb">test
  </span>Running via Spring preloader <span class="k">in </span>process 57332
  Run options: <span class="nt">--seed</span> 23226
  
  <span class="c"># Running:</span>
  
  ...............................................................................................................................................
  
  Fabulous run <span class="k">in </span>2.511829s, 56.9306 runs/s, 199.8544 assertions/s.
  143 runs, 502 assertions, 0 failures, 0 errors, 0 skips</code></pre></figure>

<p>Yep, using <code class="language-plaintext highlighter-rouge">setup</code> first then <code class="language-plaintext highlighter-rouge">before</code> works. Flip them and it blows up. Let me tell you, this one had me scratch my head for a while.</p>

<p>Okay, why does it fail, then?</p>

<p>Because of how Rails integrates Minitest, specifically in how it resolves the lifecycle hooks of the tests.</p>

<h2 id="a-dive-into-minitest-hooks-lifecycle">A dive into Minitest hooks lifecycle</h2>

<p>When Minitest executes your code, it runs some hooks around your test setup and your tests’ examples. Those hooks are:</p>
<ul>
  <li>empty methods by default</li>
  <li>meant for library and framework extensions</li>
  <li>not meant to be used in tests’ setup</li>
</ul>

<p>You can check the <a href="https://github.com/minitest/minitest/blob/339492cbaec5c460ec278e754199619d6431af35/lib/minitest/test.rb">Minitest code</a>.</p>

<p>One more thing: the <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> <code class="language-plaintext highlighter-rouge">before</code> block <a href="https://github.com/minitest/minitest/blob/339492cbaec5c460ec278e754199619d6431af35/lib/minitest/spec.rb">is just syntactic sugar</a> for <code class="language-plaintext highlighter-rouge">Minitest::Test#setup</code>. So both <code class="language-plaintext highlighter-rouge">def setup</code> and <code class="language-plaintext highlighter-rouge">before</code> block are equivalent.</p>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">┌─────────────────────────────────────────┐
│         TEST EXECUTION STARTS           │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      1. before_setup <span class="o">(</span>hook<span class="o">)</span>             │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      2. setup <span class="o">(</span>method<span class="o">)</span>                  │
│      - def setup or before block        |
│        are executed here                |
│      - Runs once before each <span class="nb">test</span>       │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      3. after_setup <span class="o">(</span>hook<span class="o">)</span>              │
└─────────────────────────────────────────┘
                    ↓
┌═════════════════════════════════════════┐
║      ★★★ MY TESTS RUN HERE ★★★          ║
└═════════════════════════════════════════┘</code></pre></figure>

<p>Whether I use the vanilla <code class="language-plaintext highlighter-rouge">def setup</code> or the <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> <code class="language-plaintext highlighter-rouge">before</code> block, Minitest treats them the same. They will both run <strong>after</strong> the <code class="language-plaintext highlighter-rouge">before_setup</code> hook.</p>

<p>Ruby on Rails, though, makes things muddy.</p>

<h2 id="ruby-on-rails-has-entered-the-chat">Ruby on Rails has entered the chat</h2>

<p>In this instance, Rails does two things that will impact my tests:</p>
<ul>
  <li>Exposes a <code class="language-plaintext highlighter-rouge">setup</code> block to use <em>in lieu of</em> the <code class="language-plaintext highlighter-rouge">def setup</code>.</li>
  <li>Hooks into Minitest by <a href="https://github.com/rails/rails/blob/cb91d817a78352fb41e33764d651991d566ae82b/activesupport/lib/active_support/test_case.rb#L219">prepending its own setup strategy</a> to <code class="language-plaintext highlighter-rouge">ActiveSupport::TestCase</code>.</li>
</ul>

<p>In its setup strategy, it hooks the <code class="language-plaintext highlighter-rouge">setup</code> block into <code class="language-plaintext highlighter-rouge">before_setup</code>.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="c1"># activesupport/lib/active_support/test_case.rb</span>
  
  <span class="k">module</span> <span class="nn">ActiveSupport</span>
    <span class="k">class</span> <span class="nc">TestCase</span> <span class="o">&lt;</span> <span class="o">::</span><span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
      <span class="no">Assertion</span> <span class="o">=</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Assertion</span>
      
      <span class="k">class</span> <span class="o">&lt;&lt;</span> <span class="nb">self</span>
        <span class="n">prepend</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Testing</span><span class="o">::</span><span class="no">SetupAndTeardown</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>
  
  <span class="c1"># activesupport/lib/active_support/testing/setup_and_teardown.rb</span>
  
  <span class="k">module</span> <span class="nn">ActiveSupport</span>
    <span class="k">module</span> <span class="nn">Testing</span>
      <span class="k">module</span> <span class="nn">SetupAndTeardown</span>
        <span class="k">module</span> <span class="nn">ClassMethods</span>
          <span class="c1"># Add a callback, which runs before &lt;tt&gt;TestCase#setup&lt;/tt&gt;.</span>
          <span class="k">def</span> <span class="nf">setup</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
            <span class="n">set_callback</span><span class="p">(</span><span class="ss">:setup</span><span class="p">,</span> <span class="ss">:before</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
          <span class="k">end</span>
        <span class="k">end</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>What it means for us:</p>
<ul>
  <li>We now have three ways of building a setup for our tests: <code class="language-plaintext highlighter-rouge">def setup</code> (vanilla Minitest), <code class="language-plaintext highlighter-rouge">setup</code> block (Rails’ Minitest), <code class="language-plaintext highlighter-rouge">before</code> block (<code class="language-plaintext highlighter-rouge">Minitest::Spec</code>)</li>
  <li>Each looks pretty similar to the next.</li>
  <li>Mixing and matching will work most of the time…</li>
  <li>BUT (!) these blocks are not executed at the same time due to the way Rails plugs its <code class="language-plaintext highlighter-rouge">setup</code> block in the Minitest lifecycle. So based on the order of declaration, some of my tests will fail!</li>
</ul>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">┌─────────────────────────────────────────┐
│         TEST EXECUTION STARTS           │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      1. before_setup <span class="o">(</span>hook<span class="o">)</span>             |
|    ⚠️ Rails plugs its setup block here  |
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      2. setup <span class="o">(</span>method<span class="o">)</span>                  │
│      - def setup or before block        |
│        are executed here                |
│      - Runs once before each <span class="nb">test</span>       │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      3. after_setup <span class="o">(</span>hook<span class="o">)</span>              │
└─────────────────────────────────────────┘
                    ↓
┌═════════════════════════════════════════┐
║      ★★★ MY TESTS RUN HERE ★★★          ║
└═════════════════════════════════════════┘</code></pre></figure>

<p>So when I mix the <code class="language-plaintext highlighter-rouge">setup</code> block and the <code class="language-plaintext highlighter-rouge">before</code> block syntaxes in a test file, they are not executed in the order I think they are.</p>

<p>No matter how deep the <code class="language-plaintext highlighter-rouge">setup</code> block is nested in my test file, in Rails, it’ll always be executed first in the <code class="language-plaintext highlighter-rouge">before_setup</code> hook.</p>

<p>So the fix to my initial problem is simple: in a Rails app when using <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>, don’t mix <code class="language-plaintext highlighter-rouge">setup</code> and <code class="language-plaintext highlighter-rouge">before</code> blocks.</p>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>Funny how such a simple mistake – mixing <code class="language-plaintext highlighter-rouge">setup</code> blocks and <code class="language-plaintext highlighter-rouge">before</code> blocks – will have you neck-deep in parts of two codebases to understand what’s what.</p>

<p>Of course, this post begs the question: why Rails did not handle the <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> syntax too?</p>

<p>I don’t have a definitive answer, but my guesses are:</p>
<ul>
  <li>Harder to inject the <code class="language-plaintext highlighter-rouge">before</code> block in the <code class="language-plaintext highlighter-rouge">before_setup</code> without hijacking Minitest (and making the connection brittle)?</li>
  <li>Because Rails is bullish on the vanilla Minitest syntax and did not want to port the Spec syntax?</li>
</ul>

<p>If you were one of the contributors who worked on this part of Rails, I’d love to know!</p>

<p>You might have noticed several things in my initial test:</p>
<ul>
  <li>Why not use <code class="language-plaintext highlighter-rouge">let</code> instead of <code class="language-plaintext highlighter-rouge">before | setup</code>? I used contrived examples for clarity. Usually, I’d use <code class="language-plaintext highlighter-rouge">let</code> for instantiating data and <code class="language-plaintext highlighter-rouge">before</code> for additional setup like authentification, stubbing, etc…</li>
  <li>Despite using <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>, my test class still uses the Rails-styled inheritance – <code class="language-plaintext highlighter-rouge">class CustomerIdentificationTest &lt; ActionDispatch::IntegrationTest</code> – instead of the <code class="language-plaintext highlighter-rouge">describe CustomerIdentification do</code> syntax one could expect. This is because <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> is not set up properly. But this will be a story for another post.</li>
</ul>

<p>Well, that was quite the rabbit hole! I now know the why behind <a href="https://remimercier.com/more-minitest-spec/">some of the gotchas</a> I’d written previously.</p>

<p>Anyway, I hope you enjoyed this one as much as I enjoyed writing it.</p>

<p>Cheers,</p>

<p>Rémi - <a href="https://ruby.social/@remi">@remi@ruby.social</a></p>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[A small mistake sent me on an overly long investigation into Minitest hooks, and how Rails integrates with these.]]></summary></entry><entry><title type="html">Reflecting on 2025</title><link href="https://remimercier.com/reflecting-on-2025/" rel="alternate" type="text/html" title="Reflecting on 2025" /><published>2026-01-30T00:00:00+00:00</published><updated>2026-01-30T00:00:00+00:00</updated><id>https://remimercier.com/reflecting-on-2025</id><content type="html" xml:base="https://remimercier.com/reflecting-on-2025/"><![CDATA[<p>2025 was the year I started something I’ve been wanting to do for a <strong>very long time</strong>: freelancing.</p>

<p>I had already been a freelancer twelve years ago, and it did not go well. I was working too much, for too little, and some clients never paid me for my work. I was young(er) but I learned my lesson: freelancing is a whole extra job, on top of your core offering.</p>

<p>So when the opportunity arose last year, I knew I needed to get better at being a “company”.</p>

<h2 id="a-year-of-freelancing">A year of freelancing</h2>

<p>I started freelancing at the very end of 2024. After a taxing four-month job search – that only emphasized how broken some tech companies are – I decided to switch my approach.</p>

<p>I reached out to Sunny – an internet pal I’ve known for years – and asked him if he needed an extra pair of hands to ship more features for his company <a href="https://cults3d.com/?utm_source=hello_from_remimercier.com" target="_blank">Cults</a>. And he said “yes”.</p>

<p>A week later, Yves – a Le Wagon alumnus – asked me if I was available to help his team tackle their more complex tasks and get some of their velocity back. And I said “yes”.</p>

<p>Both clients needed half of my time. So I was able to fill my week almost instantly.</p>

<p>Working with both Sunny and Yves has been an absolute joy. Some cool projects I built for them even inspired some <a href="/aggregate-rspec-expectations/">recent</a> <a href="/minimal-decorator-ruby/">posts</a> <a href="/minitest-spec/">on</a> <a href="/introduction-to-minitest/">this</a> <a href="/more-minitest-spec/">blog</a>.</p>

<h2 id="professional-wins">Professional wins</h2>

<p>My professional goal for 2025 was <strong>to survive the year</strong>. Between layoffs, AI coming for our jobs, and the overall LinkedIn-ish narrative, it felt as if I was doomed to switch careers (again).</p>

<p>And yet, in case you didn’t notice already, 2025 was a good year for me work-wise:</p>
<ul>
  <li>Two happy clients with whom I built (and keep building) a relationship based on trust, values and value. You can’t get better than that.</li>
  <li>A lot of projects shipped and billed which leaves me with a full year of runway.</li>
  <li>A solid progression both technically and product-wise:
    <ul>
      <li>I’ve kept digging into Ruby and Rails internals.</li>
      <li>I discovered new patterns and products.</li>
      <li>I worked hard to make sense of Typesense (pun intended).</li>
      <li>I’ve started building some foundational knowledge back on the front-end side of things (HTML, CSS, JS and the whole Hotwire).</li>
      <li>And of course, Minitest. <a href="/introduction-to-minitest/">A <em>lot</em> of Minitest</a>.</li>
    </ul>
  </li>
  <li>A lot of impactful features were shipped with little to no disruption for users. Some were critical and done on a tight schedule.</li>
  <li>Working with two teams allowed me to create bridges, and to apply what I had learned within one codebase to the other.</li>
  <li>I worked on my tooling and processes, which allowed me to deliver faster and better.</li>
  <li>In early 2025, I joined a local cooperative to which I delegate most of my admin. It’s a great way to reclaim ownership of my work environment, and the people there are both great and super helpful. This has proven to be a true multiplier for me.</li>
  <li>From the get-go, I knew I needed to work on how to run my business. I built simple processes for time tracking, invoicing, note-taking, etc. This has allowed me to spend less time on boring tasks and more time on being a true partner for both teams.</li>
</ul>

<h2 id="an-aparte-on-the-job-market">An <em>aparte</em> on the job market</h2>

<p>Whether you’re looking for a job or a freelance gig, the market has been rough for a few years now. Let’s not even talk about the rest. *gestures wildly at everything that has happened in the world*</p>

<p>But here’s my main takeaway: a lot of the “hot” companies from a few years ago now have ridiculous expectations for candidates, while offering very little in return. This has created an interesting shift: companies that weren’t sexy now attract great people. And great people get to work with smaller teams, cooler products, and often healthier finances.</p>

<h2 id="personal-wins">Personal wins</h2>

<p>I don’t like to share personal stuff on the blog. There, I said it! What can I say, I’m a <em>very</em> private person. But I’ll make an exception today.</p>

<p>The five coolest things I did this year:</p>
<ul>
  <li><strong>Making time for my family</strong>: Wednesday afternoons with the kids, lots of time off during school holidays, and a real effort to make the little ones active participants in our family life.</li>
  <li><strong>Keep working on my relationship</strong> with my partner of 16 years 🫶.</li>
  <li><strong>Getting my health in a better place</strong>: resumed boxing after a 10-year hiatus, finally got treatment for my allergies.</li>
  <li><strong>Working on my friendships</strong>: It won’t come as a surprise if I say men usually suck at understanding what makes a friendship and how to nurture one? Well, I worked on that. And I’m happy about it.</li>
  <li><strong>Realized I’m likely ADHD and/or on the spectrum</strong>: Not through a formal diagnosis, but by ticking boxes as others got theirs. I also stopped masking, and started learning what actually works for me.</li>
</ul>

<h2 id="manifesting-for-2026">Manifesting for 2026</h2>

<p>As I said earlier, my professional goal for 2025 was to survive the year. And I think I’ll keep this as one of my goals for 2026.</p>

<figure>
  <img class="regular box-shadowed" src="/media/2026/01/joel-gascogne.png" alt="a screenshot of a tweet by joel gascogne" />
  <figcaption>Surviving as a competitive advantage.</figcaption>
</figure>

<p>But I also want to leverage this good first year to plant some seeds.</p>

<p>Here’s what I’m planning:</p>
<ul>
  <li><strong>Make my clients happy</strong>: This one goes without saying. I’ll keep delivering great features, good communication, and value to the people trusting me with their business.</li>
  <li><strong>Learn HTML and CSS from the ground up</strong>: I want to develop a deeper appreciation for these languages (and build more stuff with ‘em)!</li>
  <li><strong>Invest in my freelancing practice</strong>: Time, money, work on website, nail my main value proposition, write more, etc.</li>
  <li><strong>Get help</strong>: I can’t do everything alone. This year, I’ll invest in getting help. Most notably, I need help building stronger foundations for my business and shaping a new offer.</li>
  <li><strong>Meet people as people</strong>: In the age of LLMs and the content rat race, I believe genuine connections will matter more than ever. I plan on going to more Ruby meetups, joining some of the folks at Cults at <a href="https://rubycon.it/" target="_blank">Rubicon in Rimini</a> in May, and simply getting a lot more genuine human interaction.</li>
</ul>

<p>If you’ve made it all the way to the end, thank you for reading words written by a human. You rock! (And do you know I have a <a href="/feed.xml">RSS feed</a>? Just saying.)</p>

<p>Cheers,</p>

<p>Rémi - <a href="https://ruby.social/@remi">@remi@ruby.social</a></p>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[Time to recap a good year.]]></summary></entry><entry><title type="html">More Minitest::Spec shenanigans</title><link href="https://remimercier.com/more-minitest-spec/" rel="alternate" type="text/html" title="More Minitest::Spec shenanigans" /><published>2025-10-19T00:00:00+00:00</published><updated>2025-10-19T00:00:00+00:00</updated><id>https://remimercier.com/more-minitest-spec-shenanigans</id><content type="html" xml:base="https://remimercier.com/more-minitest-spec/"><![CDATA[<p>While I already covered <a href="/minitest-spec">the basics of <code class="language-plaintext highlighter-rouge">Minitest::Spec</code></a>, I forgot to discuss a few aspects of the spec flavor. This post serves as a complement to the previous one and digs a bit deeper into some extra <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> shenanigans.</p>

<h2 id="let-over-ivar"><code class="language-plaintext highlighter-rouge">let</code> over <code class="language-plaintext highlighter-rouge">@ivar</code></h2>

<p>So far, in my setup, I only used ivars to store a <code class="language-plaintext highlighter-rouge">User</code> accessible in my test examples:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">before</span> <span class="k">do</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
      <span class="n">expect</span><span class="p">(</span><span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>However, <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> allows me to use the <code class="language-plaintext highlighter-rouge">let(:user)</code> method instead of <code class="language-plaintext highlighter-rouge">@user</code>.</p>

<p>If you use RSpec, this will look <em>very familiar</em>. If you don’t, <code class="language-plaintext highlighter-rouge">let</code> is a method exposed by the DSL, that allows you to define <a href="https://github.com/minitest/minitest/blob/master/lib/minitest/spec.rb#L261" target="_blank">memoized instance variables</a> accessible in your test examples through accessors.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span> <span class="p">}</span>

    <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
      <span class="n">expect</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>No more <code class="language-plaintext highlighter-rouge">@user</code>, just a plain <code class="language-plaintext highlighter-rouge">user</code>, as <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> <code class="language-plaintext highlighter-rouge">let</code> adds an accessor to my instance variable.</p>

<p>Also, I no longer need the <code class="language-plaintext highlighter-rouge">before do ... end</code> block to define variables. I’ll only use <code class="language-plaintext highlighter-rouge">before do ... end</code> if I need a more complex setup:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span> <span class="p">}</span>

    <span class="n">before</span> <span class="k">do</span>
      <span class="no">Account</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">user</span><span class="p">:,</span> <span class="ss">tokens: </span><span class="mi">100</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
      <span class="n">expect</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">before do ... end</code> remains the standard way to create objects not directly used in test examples or to mock objects.</p>

<h2 id="let-lazy-evaluation"><code class="language-plaintext highlighter-rouge">let</code> lazy evaluation</h2>

<p><code class="language-plaintext highlighter-rouge">let</code> allows Minitest to lazily evaluate the variable when it’s called in a test example. Repeated references of <code class="language-plaintext highlighter-rouge">let</code> within the same test example won’t trigger a re-evaluation. It means Minitest won’t run <code class="language-plaintext highlighter-rouge">User.new</code> multiple times within a test example, but beware of mutations.</p>

<p>If I share a <code class="language-plaintext highlighter-rouge">let</code> across test examples, the <code class="language-plaintext highlighter-rouge">let</code> will be re-evaluated, so I don’t need to worry about mutability. States won’t leak from one <code class="language-plaintext highlighter-rouge">it</code> case to the next.</p>

<p>Minitest does not have a <code class="language-plaintext highlighter-rouge">let!</code> method (like RSpec does). If you need to create objects required for your tests but not explicitly referenced, use the <code class="language-plaintext highlighter-rouge">before do ... end</code> method.</p>

<h2 id="use-subject-over-explicit-calls-to-the-tested-method">Use <code class="language-plaintext highlighter-rouge">subject</code> over explicit calls to the tested method</h2>

<p><code class="language-plaintext highlighter-rouge">Minitest::Spec</code> also adds the <code class="language-plaintext highlighter-rouge">subject</code> method:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">subject</span> <span class="p">{</span> <span class="n">user</span><span class="p">.</span><span class="nf">full_name</span> <span class="p">}</span>

    <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span> <span class="p">}</span>

    <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
      <span class="n">expect</span><span class="p">(</span><span class="n">subject</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">subject</code> replaces explicit calls to the method I test. It’s a syntactic convenience that lets me define the <em>subject</em> of the current scope. I find it easy to read and handy when dealing with nested contexts:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">describe</span> <span class="s2">"#full_name"</span> <span class="k">do</span>
      <span class="n">subject</span> <span class="p">{</span> <span class="n">user</span><span class="p">.</span><span class="nf">full_name</span> <span class="p">}</span>

      <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="n">last_name</span><span class="p">:)</span> <span class="p">}</span>

      <span class="n">describe</span> <span class="s2">"when the user has a one-word last name"</span> <span class="k">do</span>
        <span class="n">let</span><span class="p">(</span><span class="ss">:last_name</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"summers"</span> <span class="p">}</span>

        <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
          <span class="n">expect</span><span class="p">(</span><span class="n">subject</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
        <span class="k">end</span>
      <span class="k">end</span>

      <span class="n">describe</span> <span class="s2">"when the user has a two-word last name"</span> <span class="k">do</span>
        <span class="n">let</span><span class="p">(</span><span class="ss">:last_name</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"anne summers"</span> <span class="p">}</span>

        <span class="n">it</span> <span class="s2">"does not return the capitalized full name"</span> <span class="k">do</span>
          <span class="n">expect</span><span class="p">(</span><span class="n">subject</span><span class="p">).</span><span class="nf">wont_equal</span> <span class="s2">"Buffy Anne Summers"</span>
        <span class="k">end</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>The output for these tests is:</p>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">  lab/minitest-post → ruby user_test.rb <span class="nt">--verbose</span>
  Run options: <span class="nt">--verbose</span> <span class="nt">--seed</span> 4199

  <span class="c"># Running:</span>

  UserTest2::#full_name::when the user has a one-word last name#test_0001_returns the capitalized full name <span class="o">=</span> 0.00 s <span class="o">=</span> <span class="nb">.</span>
  UserTest2::#full_name::when the user has a two-word last name#test_0001_does not <span class="k">return </span>the capitalized full name <span class="o">=</span> 0.00 s <span class="o">=</span> <span class="nb">.</span>

  Finished <span class="k">in </span>0.001340s, 1492.5373 runs/s, 1492.5373 assertions/s.
  2 runs, 2 assertions, 0 failures, 0 errors, 0 skips</code></pre></figure>

<p>I like how combining <code class="language-plaintext highlighter-rouge">let</code> and <code class="language-plaintext highlighter-rouge">subject</code> lets me run multiple contexts while only adjusting the relevant variable. Since <code class="language-plaintext highlighter-rouge">let</code>s are lazily evaluated, the initial <code class="language-plaintext highlighter-rouge">let(:user)</code> can leverage contextual <code class="language-plaintext highlighter-rouge">let</code>s to instantiate a new <code class="language-plaintext highlighter-rouge">User</code> with the <em>ad hoc</em> local variables.</p>

<p>In plain Minitest, it’d look like this:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
    <span class="k">def</span> <span class="nf">setup</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">""</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">test_full_name_with_one_word_last_name</span>
      <span class="n">user</span><span class="p">.</span><span class="nf">last_name</span> <span class="o">=</span> <span class="s2">"summers"</span>
      <span class="n">full_name</span> <span class="o">=</span> <span class="n">user</span><span class="p">.</span><span class="nf">full_name</span>

      <span class="n">assert_equal</span> <span class="s2">"Buffy Summers"</span><span class="p">,</span> <span class="n">full_name</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">test_full_name_with_two_word_last_name</span>
      <span class="n">user</span><span class="p">.</span><span class="nf">last_name</span> <span class="o">=</span> <span class="s2">"anne summers"</span>
      <span class="n">full_name</span> <span class="o">=</span> <span class="n">user</span><span class="p">.</span><span class="nf">full_name</span>

      <span class="n">refute_equal</span> <span class="s2">"Buffy Anne Summers"</span><span class="p">,</span> <span class="n">full_name</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Since <code class="language-plaintext highlighter-rouge">@user = User.new</code> is evaluated immediately during setup, it can’t take advantage from local variables to create different variations of a <code class="language-plaintext highlighter-rouge">User</code> on the fly.</p>

<p>Note that there is no named <code class="language-plaintext highlighter-rouge">subject</code> (aka <code class="language-plaintext highlighter-rouge">subject(:name)</code>) in Minitest.</p>

<p>An aside: I guess this is one of the reasons Minitest maintainers aren’t fond of porting assertions to expectations, because on top of the work, they constantly have to deal with comparison. Which, let’s be honest, is a pain in the back.</p>

<h2 id="nested-describe-blocks">Nested <code class="language-plaintext highlighter-rouge">describe</code> blocks</h2>

<p>My last example shows that we can nest <code class="language-plaintext highlighter-rouge">describe</code> blocks!</p>

<p>There are no <code class="language-plaintext highlighter-rouge">context</code> in Minitest, but <code class="language-plaintext highlighter-rouge">describe</code> blocks are still a nicer way to show context granularity compared to plain Minitest nested classes.</p>

<p>Be careful not to nest <code class="language-plaintext highlighter-rouge">describe</code> block too deeply:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">describe</span> <span class="s2">"#full_name"</span> <span class="k">do</span>
      <span class="n">subject</span> <span class="p">{</span> <span class="n">user</span><span class="p">.</span><span class="nf">full_name</span> <span class="p">}</span>

      <span class="n">describe</span> <span class="s2">"when the user has a two-word last name"</span> <span class="k">do</span>
        <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="n">last_name</span><span class="p">:)</span> <span class="p">}</span>
        <span class="n">let</span><span class="p">(</span><span class="ss">:last_name</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"anne summers"</span> <span class="p">}</span>

        <span class="n">it</span> <span class="s2">"does not return the capitalized full name"</span> <span class="k">do</span>
          <span class="n">expect</span><span class="p">(</span><span class="n">subject</span><span class="p">).</span><span class="nf">wont_equal</span> <span class="s2">"Buffy Anne Summers"</span>
        <span class="k">end</span>

        <span class="n">describe</span> <span class="s2">"when the two-word last name is hyphenated"</span> <span class="k">do</span>
          <span class="n">let</span><span class="p">(</span><span class="ss">:last_name</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"anne-summers"</span> <span class="p">}</span>

          <span class="n">it</span> <span class="s2">"does not return the capitalized full name"</span> <span class="k">do</span>
            <span class="n">expect</span><span class="p">(</span><span class="n">subject</span><span class="p">).</span><span class="nf">wont_equal</span> <span class="s2">"Buffy Anne Summers"</span>
          <span class="k">end</span>
        <span class="k">end</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>The output for these tests becomes messy:</p>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">  lab/minitest-post → ruby user_test.rb <span class="nt">--verbose</span>
  Run options: <span class="nt">--verbose</span> <span class="nt">--seed</span> 50406

  <span class="c"># Running:</span>

  UserTest2::#full_name::when the user has a two-word last name#test_0001_does not <span class="k">return </span>the capitalized full name <span class="o">=</span> 0.00 s <span class="o">=</span> <span class="nb">.</span>
  UserTest2::#full_name::when the user has a two-word last name::when the two-word last name is hyphanated#test_0001_does not <span class="k">return </span>the capitalized full name <span class="o">=</span> 0.00 s <span class="o">=</span> <span class="nb">.</span>

  Finished <span class="k">in </span>0.001022s, 1956.9472 runs/s, 1956.9472 assertions/s.
  2 runs, 2 assertions, 0 failures, 0 errors, 0 skips</code></pre></figure>

<p>Output legibility is a common critique I hear about Minitest. At the same time, deep nesting makes reading both the files and the output difficult, no matter the framework.</p>

<h2 id="solving-the-tricky-inheritance-in-rails-apps">Solving the tricky inheritance in Rails apps</h2>

<p>Eric, from the Ruby on Rails Links Slack, pointed out the <a href="https://github.com/minitest/minitest-rails" target="_blank"><code class="language-plaintext highlighter-rouge">rails-minitest</code> gem</a>.</p>

<p>Maintained by the same people behind Minitest, <code class="language-plaintext highlighter-rouge">rails-minitest</code> provides a Minitest integration for Rails applications.</p>

<p>Its benefits include:</p>
<ul>
  <li>It allows you to explicitly declare the type of class you’re testing.</li>
  <li>It resolves <a href="/minitest-spec/#:~:text=On%20the%20other%20hand,ApplicationSystemTestCase">the inheritance shenanigans</a> through that typing.</li>
  <li>It exposes extra spec-flavored expectations for Rails mailers, jobs, routing, and more.</li>
</ul>

<h3 id="how-to-install-minitest-rails">How to install <code class="language-plaintext highlighter-rouge">minitest-rails</code></h3>

<p>The gem follows the versioning of Rails, so I added <code class="language-plaintext highlighter-rouge">gem "minitest-rails", "~&gt; 8.0.0"</code> to my Gemfile.</p>

<p><code class="language-plaintext highlighter-rouge">minitest-rails</code> comes with a handy installation generator - <code class="language-plaintext highlighter-rouge">rails generate minitest:install</code>- which adds all the necessary files to the application.</p>

<p>Be careful, though, the generator will overwrite your existing setup (<a href="/upgrading-ruby-on-rails/">much like the <code class="language-plaintext highlighter-rouge">rails app:update</code> command</a>).</p>

<p>To prevent this, I dry-ran the generator with the <code class="language-plaintext highlighter-rouge">--pretend</code> flag, which shows all the changes the gem would make: <code class="language-plaintext highlighter-rouge">rails generate minitest:install &lt;APP_PATH&gt; -p</code>.</p>

<p>The changes in my <code class="language-plaintext highlighter-rouge">application_system_test_case.rb</code> were minimal:</p>

<figure class="highlight"><pre><code class="language-zsh" data-lang="zsh">class ApplicationSystemTestCase &lt; ActionDispatch::SystemTestCase
  some existing stuff

+  register_spec_type<span class="o">(</span>self<span class="o">)</span> <span class="k">do</span> |desc, <span class="k">*</span>addl|
+    addl.include? :system
+  end

  more existing stuff
end</code></pre></figure>

<p>The generator only added <code class="language-plaintext highlighter-rouge">require "minitest/rails"</code> and some parallelization configuration to my <code class="language-plaintext highlighter-rouge">test_helper.rb</code>.</p>

<h3 id="implicit-and-explicit-test-class-typing">Implicit and explicit test class typing</h3>

<p>One of the benefits of the Minitest integration is that it lets me declare which class I’m testing with a <code class="language-plaintext highlighter-rouge">describe</code> block:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">describe</span> <span class="no">User</span> <span class="k">do</span>
    <span class="o">...</span>
  <span class="k">end</span></code></pre></figure>

<p>Instead of:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="o">...</span>
  <span class="k">end</span></code></pre></figure>

<p>I don’t need to suffix my test class with <code class="language-plaintext highlighter-rouge">Test</code> (<em>ie</em> <code class="language-plaintext highlighter-rouge">UserTest</code>) and <code class="language-plaintext highlighter-rouge">minitest-rails</code> is able to infer the inheritance from the declared class.</p>

<p>I also can cast the type explicitly to help Minitest figure out which kind of test I want to run.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">describe</span> <span class="no">User</span><span class="p">,</span> <span class="ss">:model</span> <span class="k">do</span>
    <span class="o">...</span>
  <span class="k">end</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">minitest-rails</code> even lets me declare custom types and custom inheritance, but I haven’t (yet) figured out how this works (and where I could use it).</p>

<h3 id="more-spec-flavored-expectations">More spec-flavored expectations</h3>

<p><code class="language-plaintext highlighter-rouge">minitest-rails</code> also exposes extra expectations, specifically for mailers, jobs or routing. We don’t need to choose between expectations for testing models, and assertions for testing jobs. Expectations everywhere!</p>

<p>For instance, Rails’ default Minitest syntax would look like this:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">assert_enqueued_jobs</span> <span class="mi">1</span> <span class="k">do</span>
    <span class="no">NotifyUser</span><span class="p">.</span><span class="nf">perform_later</span><span class="p">(</span><span class="ss">user_id: </span><span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
  <span class="k">end</span></code></pre></figure>

<p>And <code class="language-plaintext highlighter-rouge">minitest-rails</code> will look like this:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">must_enqueue_jobs</span> <span class="mi">1</span> <span class="k">do</span>
    <span class="no">NotifyUser</span><span class="p">.</span><span class="nf">perform_later</span><span class="p">(</span><span class="ss">user_id: </span><span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
  <span class="k">end</span></code></pre></figure>

<p>The assertion becomes an expectation, harmonizing the syntax of my tests.</p>

<h3 id="yes-but">Yes, but…</h3>

<p>While I managed to install the gem and update the declaration of my tests, one thing refused to work out of the box: mailers expectations.</p>

<p>Consider this test:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">describe</span> <span class="no">NotifyUserJob</span><span class="p">,</span> <span class="ss">:job</span> <span class="k">do</span>
    <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span> <span class="p">}</span>

    <span class="n">it</span> <span class="s2">"sends a notification email to the user"</span> <span class="k">do</span>
      <span class="n">must_enqueue_email_with</span> <span class="no">UserMailer</span><span class="p">,</span> <span class="ss">:notify</span><span class="p">,</span> <span class="ss">args: </span><span class="p">[</span><span class="n">user</span><span class="p">]</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>I used the new <code class="language-plaintext highlighter-rouge">describe</code> declaration for my test case, explicitly cast the <code class="language-plaintext highlighter-rouge">:job</code> type, and used the newly available <code class="language-plaintext highlighter-rouge">must_enqueue_email_with</code> expectation.</p>

<p>Easy, right? Well, no matter how I declared my test case, whether with implicit or explicit typing, <code class="language-plaintext highlighter-rouge">must_enqueue_email_with</code> raised a <code class="language-plaintext highlighter-rouge">NoMethodError</code>.</p>

<p>It means the <a href="https://github.com/minitest/minitest-rails/blob/master/lib/minitest/rails/expectations/active_mailer.rb" target="_blank"><code class="language-plaintext highlighter-rouge">Minitest::Rails::Expectations::ActiveMailer</code></a> module isn’t mixed-in automatically.</p>

<p>I had to include <code class="language-plaintext highlighter-rouge">Minitest::Rails::Expectations::ActionMailer</code> within my test case to make this expectation work:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="no">CalendlyEventsManagerJob</span><span class="p">,</span> <span class="ss">:integration</span> <span class="k">do</span>
  <span class="kp">include</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Rails</span><span class="o">::</span><span class="no">Expectations</span><span class="o">::</span><span class="no">ActionMailer</span>

  <span class="o">...</span>
<span class="k">end</span></code></pre></figure>

<p>There’s no mention of this in the documentation, but I’ve found this comment in the <code class="language-plaintext highlighter-rouge">ActiveJob</code> expectations module:</p>

<blockquote>
  <p>This exists as a module to allow easy mixing into classes
other than ActiveJob::TestCase where you might want to do
job testing e.g. in an Active Record model which triggers
jobs in a callback.</p>
</blockquote>

<p>So it seems there is still some manual mixin to do, despite most of the inheritance being automatically handled.</p>

<p>Is it normal? Did I do something wrong? I don’t know (yet)!</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>Well, this was a fun chase!</p>

<p>Let’s recap:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Minitest::Spec</code> also exposes <code class="language-plaintext highlighter-rouge">let</code> and <code class="language-plaintext highlighter-rouge">subject</code>.</li>
  <li>Both are lazily evaluated instance variables.</li>
  <li>With <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>, you can nest <code class="language-plaintext highlighter-rouge">describe</code> blocks for better contextualisation.</li>
  <li>The <code class="language-plaintext highlighter-rouge">minitest-rails</code> gem provides a seamless integration of Minitest into Rails.</li>
  <li>The gem provides extra methods for an overall spec-flavored test suite.</li>
  <li>In my experience, it comes with some challenges around these extra expectations, which I solved by manually including the modules. But YMMV.</li>
</ul>

<p>Big thank-yous to Cecile for pointing out the topic of <code class="language-plaintext highlighter-rouge">let</code> and <code class="language-plaintext highlighter-rouge">subject</code>, and to Eric for helping me out with setting-up the <code class="language-plaintext highlighter-rouge">minitest-rails</code> gem.</p>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[While I already covered the basics of `Minitest::Spec`, I forgot to discuss a few aspects of the spec flavor. This post serves as a complement to the previous one and digs a bit deeper into some extra `Minitest::Spec` shenanigans.]]></summary></entry><entry><title type="html">What is Minitest::Spec?</title><link href="https://remimercier.com/minitest-spec/" rel="alternate" type="text/html" title="What is Minitest::Spec?" /><published>2025-10-13T00:00:00+00:00</published><updated>2025-10-13T00:00:00+00:00</updated><id>https://remimercier.com/minitest-syntax-flavor-spec</id><content type="html" xml:base="https://remimercier.com/minitest-spec/"><![CDATA[<p>In my previous post, I talked a lot about how Minitest comes in various syntax flavors. One flavor I did not cover much is Minitest’s spec extension.</p>

<p>Before I dive in with a dedicated post about assertions, I want to cover this RSpec-style way of writing tests.</p>

<h2 id="minitest-syntax-flavors-a-recap">Minitest syntax flavors: a recap</h2>

<p>As I wrote in <a href="/introduction-to-minitest/">my previous post</a>, Minitest comes in multiple flavors:</p>
<ul>
  <li>plain Minitest: <code class="language-plaintext highlighter-rouge">def test_this_method</code></li>
  <li>Rails’ style: <code class="language-plaintext highlighter-rouge">test "this method"</code></li>
  <li>and a spec system called <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>: <code class="language-plaintext highlighter-rouge">it "tests this method"</code></li>
</ul>

<p>Each flavor changes the syntax we use to define our test files and our test examples. The changes in the DSL are minimal, in the sense that they all look familiar to Ruby developers.</p>

<p>Assertions are another matter.</p>

<p>While Rails adds a <a href="https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html" target="_blank">handful of assertions</a> (mostly around database changes), both Minitest and Rails rely on the following paradigm:</p>

<blockquote>
  <p>Check that the expected assertion holds true against the actual result.</p>
</blockquote>

<p>On the contrary, <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> relies on the (RSpec-like) opposite paradigm:</p>

<blockquote>
  <p>Check that the actual behavior matches the expected result.</p>
</blockquote>

<p>I know this sounds “po-tay-to, po-tah-to”, but bear with me for one sec.</p>

<h3 id="plain-minitest-flavor">Plain Minitest flavor:</h3>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
    <span class="k">def</span> <span class="nf">setup</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">test_returns_the_full_name</span>
      <span class="n">assert_equal</span> <span class="s2">"buffy summers"</span><span class="p">,</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<h3 id="minitestspec-flavor"><code class="language-plaintext highlighter-rouge">Minitest::Spec</code> flavor:</h3>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">before</span> <span class="k">do</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
      <span class="n">expect</span><span class="p">(</span><span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>For someone who comes from RSpec, this looks <em>very</em> familiar:</p>
<ul>
  <li>Building a setup with <code class="language-plaintext highlighter-rouge">before do ... end</code> instead of <code class="language-plaintext highlighter-rouge">setup do ... end</code>.</li>
  <li>Defining test examples with <code class="language-plaintext highlighter-rouge">it</code> instead of <code class="language-plaintext highlighter-rouge">test</code> or <code class="language-plaintext highlighter-rouge">def test_*</code>.</li>
  <li>Expectations instead of assertions: <code class="language-plaintext highlighter-rouge">expect(actual behavior).must_equal expected_result</code>.</li>
</ul>

<p>Using <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> also allows me to organize my tests into describe blocks for different contexts.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="n">before</span> <span class="k">do</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
      <span class="n">expect</span><span class="p">(</span><span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
    <span class="k">end</span>

    <span class="n">describe</span> <span class="s2">"when the user has a two-word last name"</span> <span class="k">do</span>
      <span class="n">before</span> <span class="k">do</span>
        <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"jane summers"</span><span class="p">)</span>
      <span class="k">end</span>

      <span class="n">it</span> <span class="s2">"does not return the capitalized full name"</span> <span class="k">do</span>
        <span class="n">expect</span><span class="p">(</span><span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">wont_equal</span> <span class="s2">"Buffy Jane Summers"</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Expectations can also be slightly abstracted away with the following syntax:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">it</span> <span class="s2">"returns the capitalized full name"</span> <span class="k">do</span>
    <span class="n">_</span><span class="p">(</span><span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Buffy Summers"</span>
  <span class="k">end</span></code></pre></figure>

<h2 id="how-to-enable-minitestspec">How to enable <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>?</h2>

<h3 id="in-ruby-applications">In Ruby applications</h3>

<p>In plain Ruby files, there’s no need to configure anything at the application level. Minitest comes with both flavors, plain and spec, by default.</p>

<p>The only gotcha is that test classes need to inherit from <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> instead of <code class="language-plaintext highlighter-rouge">Minitest::Test</code>. If you didn’t find this documented in the official repository, you’re not alone. It’s not documented at all. I found it <a href="https://github.com/minitest/minitest/blob/master/lib/minitest/spec.rb#L58C24-L58C38" target="_blank">in the source code</a> while investigating why my spec-style tests weren’t working.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span>
    <span class="o">...</span>
  <span class="k">end</span></code></pre></figure>

<h3 id="in-rails-applications">In Rails applications</h3>

<p>In Rails, I’ve found that you need to require <code class="language-plaintext highlighter-rouge">"minitest/spec"</code> and extend <code class="language-plaintext highlighter-rouge">Minitest::Spec::DSL</code> to the <code class="language-plaintext highlighter-rouge">ActiveSupport::TestCase</code> class.</p>

<p>Here’s an extract from my <code class="language-plaintext highlighter-rouge">test_helper.rb</code> file:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="c1"># frozen_string_literal: true</span>

  <span class="nb">require</span> <span class="s2">"rails/test_help"</span>
  <span class="nb">require</span> <span class="s2">"minitest/spec"</span>

  <span class="k">module</span> <span class="nn">ActiveSupport</span>
    <span class="k">class</span> <span class="nc">TestCase</span>
      <span class="kp">extend</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Spec</span><span class="o">::</span><span class="no">DSL</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>On the other hand, there’s no need to change the inheritance of your test classes to <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>. The inheritance in Rails is already <em>quite</em> complicated on its own, though:</p>
<ul>
  <li>Unit tests inherit from <code class="language-plaintext highlighter-rouge">ActiveSupport::TestCase</code></li>
  <li>Integration tests inherit from <code class="language-plaintext highlighter-rouge">ActionDispatch::IntegrationTest</code></li>
  <li>Unit tests for views (think helpers) inherit from <code class="language-plaintext highlighter-rouge">ActionView::TestCase</code></li>
  <li>System tests inherit from <code class="language-plaintext highlighter-rouge">ApplicationSystemTestCase</code></li>
</ul>

<h2 id="sharp-knives-and-gotchas">Sharp knives and gotchas</h2>

<p>Ruby and Rails allow you to do some weird things at your own risk. Mixing <code class="language-plaintext highlighter-rouge">setup do</code> and <code class="language-plaintext highlighter-rouge">before do</code> in the same file is one of them.</p>

<p><em>Don’t do this. This is for educational purpose only.</em></p>

<p>In plain Ruby, <code class="language-plaintext highlighter-rouge">setup</code> and <code class="language-plaintext highlighter-rouge">before</code> will work alongside one another without a hurdle.</p>

<p>In Rails, both will work if used exclusively in one file. If you mix them in one file, you have two possible outcomes:</p>

<ul>
  <li>If you use <code class="language-plaintext highlighter-rouge">setup</code> before <code class="language-plaintext highlighter-rouge">before</code>, your tests will run normally.</li>
  <li>If you use <code class="language-plaintext highlighter-rouge">before</code> before <code class="language-plaintext highlighter-rouge">setup</code>, your test will potentially fail because <code class="language-plaintext highlighter-rouge">setup</code> was not executed.</li>
</ul>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="nb">require</span> <span class="s2">"test_helper"</span>

  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span>
    <span class="n">before</span> <span class="k">do</span>
      <span class="c1"># will get executed</span>
    <span class="k">end</span>

    <span class="nb">test</span> <span class="s2">"some method"</span> <span class="k">do</span>
      <span class="c1"># will pass</span>
    <span class="k">end</span>

    <span class="n">describe</span> <span class="s2">"a nested context"</span> <span class="k">do</span>
      <span class="n">setup</span> <span class="k">do</span>
        <span class="c1"># will not be executed</span>
      <span class="k">end</span>

      <span class="n">it</span> <span class="s2">"returns some result"</span> <span class="k">do</span>
        <span class="c1"># will potentially fail</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Now that we’re more familiar with <code class="language-plaintext highlighter-rouge">Minitest::Spec</code>, we have access to a new way of writing assertions, such as <code class="language-plaintext highlighter-rouge">must_equal</code>, <code class="language-plaintext highlighter-rouge">must_match</code>, and others. I’ll cover those in a future post.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>I hope this post helped clarify the basics of the <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> syntax.</p>

<p>The TL;DR is:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Minitest::Spec</code> is an extension of <code class="language-plaintext highlighter-rouge">Minitest::Test</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">Minitest::Spec</code> is the third main flavor of Minitest.</li>
  <li><code class="language-plaintext highlighter-rouge">Minitest::Spec</code> provides a bridge between assertions and spec-style expectations.</li>
  <li>Adding <code class="language-plaintext highlighter-rouge">Minitest::Spec</code> to Ruby or Rails comes with some gotchas.</li>
</ul>

<p>The third post of this series – <a href="/more-minitest-spec/">More Minitest::Spec shenanigans</a> – is live.</p>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[In my previous post, I talked a lot about how Minitest comes in various syntax flavors. One flavor I did not cover much is Minitest's spec extension.]]></summary></entry><entry><title type="html">Marketing Haikus</title><link href="https://remimercier.com/marketing-haikus/" rel="alternate" type="text/html" title="Marketing Haikus" /><published>2025-10-09T00:00:00+00:00</published><updated>2025-10-09T00:00:00+00:00</updated><id>https://remimercier.com/haikus</id><content type="html" xml:base="https://remimercier.com/marketing-haikus/"><![CDATA[<p>Just a list of haikus, mostly written when I was <del>working</del> dying of boredom in marketing. Making fun as a tool to push through.</p>

<div>
  
    <h2>2016</h2>

    

    
      <p>PH sends crazy traffic <br />
A scream splits the office <br />
Forgot the sign-up form on my website
</p>
      <p>---</p>
    
      <p>Outbound marketing is dead <br />
They love data now <br />
Who wants my huge billboard
</p>
      <p>---</p>
    
      <p>Growth hacker they say <br />
Hottest job right now <br />
Crunch data in excel
</p>
      <p>---</p>
    
      <p>PR agency nowadays <br />
Is 2.0 <br />
Starts its tweets with a handle
</p>
      <p>---</p>
    
      <p>Beta launch due <br />
Bug riddled <br />
Push to prod anyway
</p>
      <p>---</p>
    
      <p>SEO course today <br />
Teacher says <br />
Meta keywords rock
</p>
      <p>---</p>
    
      <p>Tweet about growth hacking <br />
So many retweets <br />
IFTT bots only
</p>
      <p>---</p>
    
  
</div>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[Just a list of haikus, mostly written when I was working (and being bored out of my wits) in marketing. Making fun as a tool to push through.]]></summary></entry><entry><title type="html">Lost in Minitest? Start here!</title><link href="https://remimercier.com/introduction-to-minitest/" rel="alternate" type="text/html" title="Lost in Minitest? Start here!" /><published>2025-10-07T00:00:00+00:00</published><updated>2025-10-07T00:00:00+00:00</updated><id>https://remimercier.com/test-your-rails-app-with-minitest-a-much-needed-tutorial</id><content type="html" xml:base="https://remimercier.com/introduction-to-minitest/"><![CDATA[<p>I have a confession to make: I have never used Minitest in the seven years I’ve been a professional programmer.</p>

<p>I’ve always used <a href="/series/rspec/">the <em>other framework</em></a>.</p>

<p>But earlier this year, I started working with a client whose application relied solely on QA instead of automated tests. In an effort to bring the team peace of mind during releases, I started adding tests to the most critical parts of the application.</p>

<p>Lured by the promise of speed and wide adoption, I suggested we try Minitest.</p>

<p>As I started working on writing my first tests, I hit an unexpected roadblock.</p>

<h2 id="minitest-lack-of-onboarding">Minitest (lack of) onboarding</h2>

<p>After writing several hundred tests, I can confidently say that <strong>Minitest’s biggest weakness is its onboarding</strong>. The information is so awfully scattered, so sparse and so obscure that it makes navigating your tax returns friendlier in comparison.</p>

<p>As a newcomer, I would have loved (let’s forget about love, <strong>I would have needed!</strong>) to get the main information right off the bat:</p>
<ul>
  <li>What does Minitest do?</li>
  <li>Why does Minitest do what it does?</li>
  <li>How do you make Minitest do what it does?</li>
</ul>

<p>Instead, Minitest’s <a href="https://github.com/minitest/minitest" target="_blank">official repository</a> is a piece of writing more akin to Joyce’s Ulysses than to a technical documentation.</p>

<p>Programmers get welcomed with a list of footer-worthy links, some congratulatory quotes, and hints that you should get a mental health check for having ever used RSpec.</p>

<p>You have to scroll past this cruft to get the first useful nugget of information: a breakdown of Minitest main components.</p>

<h2 id="minitest-101">Minitest 101</h2>

<p>Minitest is a prominent testing framework for Ruby code. It’s the default testing framework embedded by default in <a href="https://ruby-doc.org/stdlib-2.7.4/libdoc/minitest/rdoc/Minitest.html" target="_blank">Ruby</a> and <a href="https://github.com/rails/rails/blob/main/activesupport/lib/active_support/test_case.rb" target="_blank">Ruby on Rails</a>.</p>

<p>If you generate a new Rails app today, or if you have an existing app with no other test framework configured, you can run <code class="language-plaintext highlighter-rouge">rails test</code>, and Rails will find the <code class="language-plaintext highlighter-rouge">test</code> folder and check for test files.</p>

<p>Otherwise, it’s just a matter of adding <code class="language-plaintext highlighter-rouge">gem 'minitest'</code> to your Gemfile, and off you go.
Minitest provides several components, each addressing a specific need. Here are a few:</p>
<ul>
  <li><a href="https://docs.seattlerb.org/minitest/Minitest/Test.html" target="_blank">minitest/test</a> provides a set of assertions to test your code.</li>
  <li><a href="https://docs.seattlerb.org/minitest/Minitest/Expectations.html" target="_blank">minitest/spec</a> enables the use of RSpec-like matchers.</li>
  <li><a href="https://docs.seattlerb.org/minitest/Minitest/Mock.html">minitest/mock</a> is a lightweight mocking (and stubbing) library.</li>
</ul>

<p>Minitest uses the concept of <strong>assertions</strong> which lets you verify that the expected result matches the actual result:</p>

<blockquote>
  <p>Check that this condition holds true.</p>
</blockquote>

<p>Coming from RSpec, I’m used to the concept of <strong>expectations</strong> which take the opposite perspective of Minitest’s assertions:</p>

<blockquote>
  <p>This object should behave this way.</p>
</blockquote>

<h2 id="minitest-syntax-flavors--everything-everywhere-all-at-once">Minitest syntax flavors : everything, everywhere, all at once</h2>

<p>One thing that slowed down my adoption is that Minitest offers <strong>multiple styles of syntaxes</strong>. You can write the same test in several ways, sometimes even mixing styles together. Each style lives in its own module or extension.</p>

<p>RSpec is either loved or hated for its DSL, but it gives me one major advantage: I don’t have to decide for every test which style to use. <a href="/pick-a-standard/">I use the standard and I move on</a>.</p>

<p>With Minitest, though, I can choose from:</p>
<ul>
  <li>the default syntax</li>
  <li>Rails’ custom syntax</li>
  <li>or Minitest’s spec syntax (which mimic RSpec’s style)</li>
</ul>

<p>Let’s write some tests so you get my point.</p>

<h2 id="writing-my-first-tests-with-minitest">Writing my first tests with Minitest</h2>

<p>Let’s say I have this plain Ruby <code class="language-plaintext highlighter-rouge">User</code> class:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">User</span>
    <span class="nb">attr_accessor</span> <span class="ss">:first_name</span><span class="p">,</span> <span class="ss">:last_name</span>

    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">first_name</span><span class="p">:,</span> <span class="n">last_name</span><span class="p">:)</span>
      <span class="vi">@first_name</span> <span class="o">=</span> <span class="n">first_name</span>
      <span class="vi">@last_name</span> <span class="o">=</span> <span class="n">last_name</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">full_name</span>
      <span class="s2">"</span><span class="si">#{</span><span class="n">first_name</span><span class="p">.</span><span class="nf">capitalize</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">last_name</span><span class="p">.</span><span class="nf">capitalize</span><span class="si">}</span><span class="s2">"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>And I want to write a simple test for it:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="nb">require</span> <span class="s2">"minitest/autorun"</span>
  <span class="nb">require_relative</span> <span class="s2">"user"</span>

  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
    <span class="k">def</span> <span class="nf">setup</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">test_returns_the_full_name</span>
      <span class="n">assert_equal</span> <span class="s2">"buffy summers"</span><span class="p">,</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>In this case, I’m using plain Minitest. It’s the default syntax, with the simplest setup.</p>

<p>Several remarks:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">require "minitest/autorun"</code> instructs Ruby to load Minitest and run the tests once the file is loaded.</li>
  <li><code class="language-plaintext highlighter-rouge">UserTest</code> inherits from <code class="language-plaintext highlighter-rouge">Minitest::Test</code>, which allows me to use Minitest’s syntax.</li>
  <li>My test method <code class="language-plaintext highlighter-rouge">def test_returns_the_full_name</code> is prefixed with <code class="language-plaintext highlighter-rouge">test_</code> which is a <a href="https://github.com/minitest/minitest?tab=readme-ov-file#label-Unit+tests" target="_blank">Minitest convention</a>.</li>
  <li>The <code class="language-plaintext highlighter-rouge">assert_equal</code> method takes the expected result first, the actual result second.</li>
  <li><code class="language-plaintext highlighter-rouge">def setup; end</code> lets me create my test setup before my code run, similar to RSpec’s <code class="language-plaintext highlighter-rouge">before do ... end</code>).</li>
  <li>I use ivars to access the objects I set up for my test examples.</li>
</ul>

<p>Here’s the output for this first test:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">  lab/minitest-post → ruby user_test.rb
  Run options: <span class="nt">--seed</span> 64661

  <span class="c"># Running:</span>

  <span class="nb">.</span>

  Finished <span class="k">in </span>0.001239s, 807.1025 runs/s, 807.1025 assertions/s.
  1 runs, 1 assertions, 0 failures, 0 errors, 0 skips</code></pre></figure>

<p>If my test were to failed:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">  lab/minitest-post → ruby user_test.rb
  Run options: <span class="nt">--seed</span> 22116

  <span class="c"># Running:</span>

  F

  Failure:
  UserTest#test_returns_the_full_name <span class="o">[</span>user_test.rb:10]:
  Expected: <span class="s2">"buffy summers"</span>
    Actual: <span class="s2">"Buffy Summers"</span>

  bin/rails <span class="nb">test </span>user_test.rb:9

  Finished <span class="k">in </span>0.001472s, 679.3478 runs/s, 679.3478 assertions/s.
  1 runs, 1 assertions, 1 failures, 0 errors, 0 skips</code></pre></figure>

<h3 id="the-same-test-but-in-rails">The same test, but in Rails</h3>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
    <span class="k">def</span> <span class="nf">full_name</span>
      <span class="s2">"</span><span class="si">#{</span><span class="n">first_name</span><span class="p">.</span><span class="nf">capitalize</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">last_name</span><span class="p">.</span><span class="nf">capitalize</span><span class="si">}</span><span class="s2">"</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="nb">require</span> <span class="s2">"test_helper"</span>

  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span>
    <span class="k">def</span> <span class="nf">setup</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="nb">test</span> <span class="s2">"returns the full name"</span> <span class="k">do</span>
      <span class="n">assert_equal</span> <span class="s2">"Buffy Summers"</span><span class="p">,</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>What’s changed:</p>
<ul>
  <li>My <code class="language-plaintext highlighter-rouge">User</code> now inherits from <code class="language-plaintext highlighter-rouge">ApplicationRecord</code> (because Rails).</li>
  <li>My test file starts with <code class="language-plaintext highlighter-rouge">require "test_helper"</code>, which loads the Rails testing environment (database, fixtures, helpers, etc.)</li>
  <li><code class="language-plaintext highlighter-rouge">UserTest</code> now inherits from <code class="language-plaintext highlighter-rouge">ActiveSupport::TestCase</code>, which inherits from <code class="language-plaintext highlighter-rouge">Minitest::Test</code>. This allows Rails to define additional assertions.</li>
  <li>Minitest’s test method definition (<code class="language-plaintext highlighter-rouge">def test_returns_the_full_name</code>) is now abstracted into the Rails DSL (<code class="language-plaintext highlighter-rouge">test "returns the full name"</code>). This syntax is just a method, <code class="language-plaintext highlighter-rouge">test</code>, that takes a description and a block as arguments (<a href="https://github.com/rails/rails/blob/main/activesupport/lib/active_support/testing/declarative.rb" target="_blank">see the code source</a>).</li>
</ul>

<p>Since <code class="language-plaintext highlighter-rouge">ActiveSupport::TestCase</code> inherits from <code class="language-plaintext highlighter-rouge">Minitest::Test</code>, I can also mix and match syntaxes if I ever feel so inclined:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="nb">require</span> <span class="s2">"test_helper"</span>

  <span class="k">class</span> <span class="nc">UserTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span>
    <span class="n">setup</span> <span class="k">do</span>
      <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">first_name: </span><span class="s2">"buffy"</span><span class="p">,</span> <span class="ss">last_name: </span><span class="s2">"summers"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">test_returns_the_full_name</span>
      <span class="n">assert_equal</span> <span class="s2">"buffy summers"</span><span class="p">,</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">full_name</span>
    <span class="k">end</span>

    <span class="nb">test</span> <span class="s2">"returns the user slug"</span> <span class="k">do</span>
      <span class="n">assert_equal</span> <span class="s2">"buffy_summers"</span><span class="p">,</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">slug</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Here, I use both Minitest syntax and the Rails DSL to define test examples in the same file. Does it work? Yep! Should I do it? I’d rather not!</p>

<p>But I wanted to show you that you can, even if you shouldn’t.</p>

<p>I did not even toggle <code class="language-plaintext highlighter-rouge">minitest/spec</code> which opens up a third syntax to set up and define your test examples. I’ll cover this in another post.</p>

<p>One thing I’m not showing here that you should know. In Rails, based on your type of test, you’ll want your test file to inherit from a different class:</p>
<ul>
  <li>Unit tests, inherit from <code class="language-plaintext highlighter-rouge">ActiveSupport::TestCase</code></li>
  <li>Integration tests, inherit from <code class="language-plaintext highlighter-rouge">ActionDispatch::IntegrationTest</code></li>
  <li>Unit tests for views (think helpers), inherit from <code class="language-plaintext highlighter-rouge">ActionView::TestCase</code></li>
  <li>System tests, inherit from <code class="language-plaintext highlighter-rouge">ApplicationSystemTestCase</code></li>
</ul>

<h2 id="wrapping-up">Wrapping up</h2>

<p>As always, I intended this post to be short, and failed.</p>

<p>If you’re starting out with Minitest and feeling a bit lost, I hope this post helped you understand the basics.</p>

<p>The TL;DR is:</p>
<ul>
  <li>Minitest is part of Ruby standard library.</li>
  <li>Minitest is part of Rails <code class="language-plaintext highlighter-rouge">ActiveSupport</code>.</li>
  <li>Minitest has several possible syntaxes to define your test examples.</li>
  <li>Minitest has a multitude of assertions (we’ll cover those in the next post).</li>
  <li>Minitest is obscure at first, but once you get the hang of it, it’s quite neat.</li>
</ul>

<p>The second part of this series – <a href="/minitest-spec/">What is Minitest::Spec</a> – is now live!</p>

<p>Thank you to <a href="https://www.cecilitse.org/" target="_blank">Cecile</a> for her suggestions.</p>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[I have a confession to make: I have never used Minitest in the seven years I've been a professional programmer. Lured by the promise of speed and wide adoption, I decided to try Minitest. Then I hit an unexpected roadblock.]]></summary></entry><entry><title type="html">Finally learning a new language</title><link href="https://remimercier.com/learning-logo" rel="alternate" type="text/html" title="Finally learning a new language" /><published>2025-09-12T00:00:00+00:00</published><updated>2025-09-12T00:00:00+00:00</updated><id>https://remimercier.com/learning-a-new-language</id><content type="html" xml:base="https://remimercier.com/learning-logo"><![CDATA[<p>For years, people around me have been telling me to learn new programming languages—that I shouldn’t corner myself with Ruby (and Rails). And for years, I couldn’t muster the mental energy to do it.</p>

<p>But lo and behold, the breakthrough happened!</p>

<p>Will it be Rust? Will it be JavaScript?</p>

<p>None of the above, my good sir! It’ll be <a href="https://en.wikipedia.org/wiki/Logo_(programming_language)" target="_blank">Logo</a>, a procedural language designed to teach children the basics of programming.</p>

<p>Why, though? Well, last week, my child’s new teacher told us he intended to introduce programming to the class. And I thought: “Ooooh, I might finally be able to explain to my children what I do for a living.”</p>

<p>So, I got <em>very</em> excited about it and <a href="https://www.youtube.com/watch?v=cwnMhI9XjO8" target="_blank">down the rabbit hole I fell</a>.</p>

<p>To make it extra fun, I designed a first 30-minute workshop with plenty of games.</p>

<p>Here’s my pitch to present the basic concepts to the kids:</p>

<blockquote>
  <p>Today, we’re going to control a computer. And to make a computer do cool things, we have to give it orders.</p>

  <p> </p>

  <p>Orders are also called <strong>instructions</strong>.</p>

  <p> </p>

  <p>An instruction can be short: <em>“Take one step forward.”</em></p>

  <p> </p>

  <p>Or it can be long: <em>“Take one step forward, turn around, walk three steps hopping on your left foot while putting your right pinky in your nostril, fart loudly, laugh even louder, and do a cartwheel.”</em></p>

  <p> </p>

  <p>When we put instructions together to make a computer do something, we build what’s called a <strong>program</strong>.</p>

  <p> </p>

  <p>And a program is written in a language that the computer can understand: a <strong>programming language</strong>.</p>

  <p> </p>

  <p>You, for example, speak French and a little bit of English. Well, the computer speaks C, Lisp, or Logo.</p>

  <p> </p>

  <p>And that works out nicely, because today we’re going to talk to our computer in Logo.</p>
</blockquote>

<p>I plan to have them team up to draw the initial of their first name using Logo instructions. I will get inspiration from Seymour Papert — one of the inventors of Logo — and have the kids use physical space to walk through and talk about the problem.</p>

<p>My current plan:</p>
<ul>
  <li>Draw the two letters on the floor with masking tape.</li>
  <li>Build a set of cardboard jigsaw puzzle pieces that represent the instructions (in French).</li>
  <li>Have the kids pick the jigsaw pieces and assemble them on the tape to find the correct instructions and the correct order of operations.</li>
  <li>Have them type the instructions into the terminal and see if the output matches our expected result.</li>
  <li>Take a few pictures and do a quick write-up with them so they can present it in their classes.</li>
</ul>

<p>Some ideas for my jigsaw puzzle pieces:</p>

<figure class="highlight"><pre><code class="language-txt" data-lang="txt">                        | \
+-----------------------+   \
|       avance 50            &gt;
+-----------------------+   /
                        | /


       *******
     * tourne  *
    *  droite   *
    *    90    *
     *   |_   *
      *******</code></pre></figure>

<p>It’s been a long time since I got that excited about a side project! Let’s hope I won’t bore the kids to death!</p>]]></content><author><name>Rémi Mercier</name></author><category term="other" /><summary type="html"><![CDATA[For years, people around me have been telling me to learn new programming languages—that I shouldn’t corner myself with Ruby (and Rails). And for years, I couldn’t muster the mental energy to do it.]]></summary></entry><entry><title type="html">Build a minimal decorator with Ruby in 30 minutes</title><link href="https://remimercier.com/minimal-decorator-ruby/" rel="alternate" type="text/html" title="Build a minimal decorator with Ruby in 30 minutes" /><published>2025-06-12T00:00:00+00:00</published><updated>2025-06-12T00:00:00+00:00</updated><id>https://remimercier.com/how-to-build-a-minimal-decorator-with-ruby</id><content type="html" xml:base="https://remimercier.com/minimal-decorator-ruby/"><![CDATA[<p>A few weeks ago, I needed to add some view-related methods to an object. Decorators are my go-to pattern to handle this kind of logic.</p>

<p>Normally, I’d use the <a href="https://github.com/drapergem/draper" target="_blank">draper</a> gem to build decorators. But the app I’m working on used an older and incompatible version of Rails.</p>

<p>So I built a minimal decorator from scratch, added a bunch of extra behaviors, only to end up abstracting all of these away. Follow along!</p>

<h2 id="what-im-working-with">What I’m working with</h2>

<p>My <code class="language-plaintext highlighter-rouge">Teacher</code> class has a handful of methods:</p>
<ul>
  <li>A one-to-many relationship with the <code class="language-plaintext highlighter-rouge">Student</code> class.</li>
  <li>Two public methods: one that exposes the maximum number of students a teacher can teach to, and one exposing the available teaching places.</li>
</ul>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">Teacher</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
    <span class="n">has_many</span> <span class="ss">:students</span>

    <span class="k">def</span> <span class="nf">maximum_number_of_students</span> <span class="o">=</span> <span class="mi">30</span>

    <span class="k">def</span> <span class="nf">available_places</span>
      <span class="p">(</span><span class="n">maximum_number_of_students</span> <span class="o">&lt;=&gt;</span> <span class="n">students</span><span class="p">.</span><span class="nf">size</span><span class="p">).</span><span class="nf">clamp</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>In my views, I want to display a table of teachers where the number of available places for each teacher is backed by a background colour.</p>

<p><img class="large" src="/media/2025/06/minimal-decorator-table-example-remi-mercier.png" alt="a screenshot of a table with teachers names and available spaces for each" /></p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># teachers/index.html.erb</span>

<span class="o">&lt;</span><span class="n">table</span> <span class="k">class</span><span class="o">=</span><span class="s2">"table table-striped"</span><span class="o">&gt;</span>
  <span class="o">&lt;</span><span class="n">thead</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="n">tr</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="n">th</span><span class="o">&gt;</span><span class="no">Name</span> <span class="n">of</span> <span class="n">the</span> <span class="n">teacher</span><span class="o">&lt;</span><span class="sr">/th&gt;
      &lt;th&gt;Available places&lt;/</span><span class="n">th</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="sr">/tr&gt;
  &lt;/</span><span class="n">thead</span><span class="o">&gt;</span>
  <span class="o">&lt;</span><span class="n">tbody</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="sx">% teachers.each </span><span class="k">do</span> <span class="o">|</span><span class="n">teacher</span><span class="o">|</span> <span class="sx">%&gt;
      &lt;tr&gt;</span>
        <span class="o">&lt;</span><span class="n">td</span><span class="o">&gt;&lt;</span><span class="sx">%= teacher.full_name %&gt;&lt;/td&gt;
        &lt;td class=</span><span class="s2">"&lt;%= teacher.colour_coded_availability %&gt;"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="sx">%= teacher.available_places %&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
    &lt;% end %&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</span></code></pre></figure>

<p>I could write the <code class="language-plaintext highlighter-rouge">Teacher#colour_coded_availability</code> method in my model like so:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="k">class</span> <span class="nc">Teacher</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
    <span class="n">has_many</span> <span class="ss">:students</span>

    <span class="k">def</span> <span class="nf">maximum_number_of_students</span> <span class="o">=</span> <span class="mi">30</span>

    <span class="k">def</span> <span class="nf">available_places</span>
      <span class="p">(</span><span class="n">maximum_number_of_students</span> <span class="o">&lt;=&gt;</span> <span class="n">students</span><span class="p">.</span><span class="nf">size</span><span class="p">).</span><span class="nf">clamp</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">colour_coded_availability</span>
      <span class="k">case</span> <span class="n">available_places</span>
      <span class="k">when</span> <span class="mi">0</span> <span class="k">then</span> <span class="s2">"bg-colour-red"</span>
      <span class="k">else</span> <span class="s2">"bg-colour-green"</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>However, models are not the place for methods generating CSS classes. Decorators are!</p>

<h2 id="drafting-a-decorator">Drafting a decorator</h2>

<p>My decorator should accept an instance of <code class="language-plaintext highlighter-rouge">Teacher</code> and expose the <code class="language-plaintext highlighter-rouge">colour_coded_availability</code> public method.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/decorators/teacher_decorator.rb</span>

  <span class="k">class</span> <span class="nc">TeacherDecorator</span>
    <span class="nb">attr_reader</span> <span class="ss">:teacher</span>

    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">teacher</span><span class="p">:)</span>
      <span class="vi">@teacher</span> <span class="o">=</span> <span class="n">teacher</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">colour_coded_availability</span>
      <span class="k">case</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">available_places</span>
      <span class="k">when</span> <span class="mi">0</span> <span class="k">then</span> <span class="s2">"bg-colour-red"</span>
      <span class="k">else</span> <span class="s2">"bg-colour-green"</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Now, I can instantiate my decorator and use it in my views:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/controllers/teachers_controller.rb</span>
  <span class="k">class</span> <span class="nc">TeachersController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
    <span class="k">def</span> <span class="nf">index</span>
      <span class="vi">@teachers</span> <span class="o">=</span> <span class="no">Teacher</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="no">TeacherDecorator</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">teacher: </span><span class="n">_1</span><span class="p">)</span> <span class="p">}</span>
    <span class="k">end</span>
  <span class="k">end</span>

<span class="c1"># teachers/index.html.erb</span>
  <span class="o">&lt;</span><span class="n">table</span> <span class="k">class</span><span class="o">=</span><span class="s2">"table table-striped"</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="n">thead</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="n">tr</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="n">th</span><span class="o">&gt;</span><span class="no">Name</span> <span class="n">of</span> <span class="n">the</span> <span class="n">teacher</span><span class="o">&lt;</span><span class="sr">/th&gt;
        &lt;th&gt;Available places&lt;/</span><span class="n">th</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="sr">/tr&gt;
    &lt;/</span><span class="n">thead</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="n">tbody</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="sx">% @teachers.each </span><span class="k">do</span> <span class="o">|</span><span class="n">teacher</span><span class="o">|</span> <span class="sx">%&gt;
        &lt;tr&gt;</span>
          <span class="o">&lt;</span><span class="n">td</span><span class="o">&gt;&lt;</span><span class="sx">%= teacher.full_name %&gt;&lt;/td&gt;
          &lt;td class=</span><span class="s2">"&lt;%= teacher.colour_coded_availability %&gt;"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="sx">%= teacher.available_places %&gt;
          &lt;/td&gt;
        &lt;/tr&gt;
      &lt;% end %&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;</span></code></pre></figure>

<p>When I can call <code class="language-plaintext highlighter-rouge">teacher.colour_coded_availability</code> in my views, the method retrieves a CSS class and adds it to the HTML <code class="language-plaintext highlighter-rouge">&lt;td&gt;</code> tag.</p>

<p>But if I were to run this code as is, I’d get a beautiful <code class="language-plaintext highlighter-rouge">NoMethodError</code>. Why?</p>

<p>My views do not handle instances of <code class="language-plaintext highlighter-rouge">Teacher</code> anymore. They handle instances of <code class="language-plaintext highlighter-rouge">TeacherDecorator</code>. So, when I’m calling the public methods defined on <code class="language-plaintext highlighter-rouge">Teacher</code>, the decorator doesn’t know what to do with them.</p>

<p>My decorator needs to be able to handle both its own public methods and the public methods defined on the underlying record (<code class="language-plaintext highlighter-rouge">Teacher</code>, in this case).</p>

<p>And we do that by using Ruby’s <code class="language-plaintext highlighter-rouge">method_missing</code>.</p>

<h2 id="rubys-method_missing-to-the-rescue">Ruby’s <code class="language-plaintext highlighter-rouge">method_missing</code> to the rescue</h2>

<p><code class="language-plaintext highlighter-rouge">method_missing</code> is how Ruby handles method calls made on objects where said methods are not defined. Ruby passes the method call along <a href="/beginners-introduction-to-ruby-classes-objects/">the ancestry chain</a> until it can either resolves it or raises a <code class="language-plaintext highlighter-rouge">NoMethodError</code>.</p>

<p>When I call <code class="language-plaintext highlighter-rouge">@teacher.full_name</code>, I want my decorator to rescue the <code class="language-plaintext highlighter-rouge">NoMethodError</code>, and forward <code class="language-plaintext highlighter-rouge">#full_name</code> to the underlying instance of <code class="language-plaintext highlighter-rouge">Teacher</code>.</p>

<p>To do that, I need to re-open Ruby’s <code class="language-plaintext highlighter-rouge">method_missing</code>, add a custom behavior, then allow <code class="language-plaintext highlighter-rouge">method_missing</code> to run its normal course.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">TeacherDecorator</span>
  <span class="nb">attr_reader</span> <span class="ss">:teacher</span>

  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">teacher</span><span class="p">)</span>
    <span class="vi">@teacher</span> <span class="o">=</span> <span class="n">teacher</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">availability_as_background</span>
    <span class="k">case</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">max_number_of_students</span> <span class="o">&lt;=&gt;</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">available_places</span>
    <span class="k">when</span> <span class="o">-</span><span class="mi">1</span> <span class="k">then</span> <span class="s2">"background-danger"</span>
    <span class="k">when</span> <span class="mi">0</span> <span class="k">then</span> <span class="s2">"background-warning"</span>
    <span class="k">when</span> <span class="mi">1</span> <span class="k">then</span> <span class="s2">"background-success"</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="kp">private</span>

  <span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">public_send</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> <span class="k">if</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="nb">method</span><span class="p">)</span>

    <span class="k">super</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">respond_to_missing?</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">include_private</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
    <span class="n">teacher</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="o">||</span> <span class="k">super</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>In this example, I keep the original signature of Ruby’s <code class="language-plaintext highlighter-rouge">method_missing</code>.</p>

<p>The only thing I tweak is forwarding the method call to the underlying <code class="language-plaintext highlighter-rouge">teacher</code>. I only forward it if the <code class="language-plaintext highlighter-rouge">teacher</code> responds to the method. Then, I let Ruby resume its original behavior <sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<p>Now, <code class="language-plaintext highlighter-rouge">@teacher.full_name</code> is properly forwarded to the underlying instance of <code class="language-plaintext highlighter-rouge">Teacher</code>.</p>

<p>What would be cool now, is to allow other decorators to share this behavior.</p>

<h2 id="normalizing-the-behavior-to-create-other-decorators">Normalizing the behavior to create other decorators</h2>

<p>One way to gather default behavior shared across various decorators is to rely on inheritance. I can create an <code class="language-plaintext highlighter-rouge">ApplicationDecorator</code> whose job is to handle instantiation, and forwarding method calls to the underlying record.</p>

<p>Then, I can have my <code class="language-plaintext highlighter-rouge">TeacherDecorator</code> inherit from the <code class="language-plaintext highlighter-rouge">ApplicationDecorator</code>.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">ApplicationDecorator</span>
  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
    <span class="vi">@record</span> <span class="o">=</span> <span class="n">record</span>
  <span class="k">end</span>

  <span class="kp">private</span>

  <span class="nb">attr_reader</span> <span class="ss">:record</span>

  <span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span><span class="o">...</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">record</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="nb">method</span><span class="p">)</span>
      <span class="n">record</span><span class="p">.</span><span class="nf">public_send</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>
    <span class="k">else</span>
      <span class="k">super</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">respond_to_missing?</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">include_private</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
    <span class="n">record</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="o">||</span> <span class="k">super</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">class</span> <span class="nc">TeacherDecorator</span> <span class="o">&lt;</span> <span class="no">ApplicationDecorator</span>
  <span class="nb">attr_reader</span> <span class="ss">:teacher</span>

  <span class="k">def</span> <span class="nf">availability_as_background</span>
    <span class="k">case</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">max_number_of_students</span> <span class="o">&lt;=&gt;</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">available_places</span>
    <span class="k">when</span> <span class="o">-</span><span class="mi">1</span> <span class="k">then</span> <span class="s2">"background-danger"</span>
    <span class="k">when</span> <span class="mi">0</span> <span class="k">then</span> <span class="s2">"background-warning"</span>
    <span class="k">when</span> <span class="mi">1</span> <span class="k">then</span> <span class="s2">"background-success"</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="kp">private</span>

  <span class="kp">alias_method</span> <span class="ss">:teacher</span><span class="p">,</span> <span class="ss">:record</span>
<span class="k">end</span></code></pre></figure>

<p>My <code class="language-plaintext highlighter-rouge">TeacherDecorator</code> doesn’t need to bother about its initialization since it’s handled by the parent <code class="language-plaintext highlighter-rouge">ApplicationDecorator</code>. The only thing I added, is the ability to reference the <code class="language-plaintext highlighter-rouge">record</code> as <code class="language-plaintext highlighter-rouge">teacher</code> so it’s clearer what kind of record we’re working with.</p>

<h2 id="ensure-rails-default-behavior-works-well">Ensure Rails default behavior works well</h2>

<p>Some Rails native helpers will have a hard time handling my decorator.</p>

<p>Consider this code:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="sb">`edit_teacher_path(@teacher)`</span> <span class="c1"># =&gt; Should generate teachers/1/edit</span></code></pre></figure>

<p>But if <code class="language-plaintext highlighter-rouge">@teacher</code> references an instance of my <code class="language-plaintext highlighter-rouge">TeacherDecorator</code>, the generated path is <code class="language-plaintext highlighter-rouge">teachers/#TeacherDecorator/edit</code>.</p>

<p>How do I make my decorator integrate with Rails default behavior?</p>

<p>I can re-open the <code class="language-plaintext highlighter-rouge">to_param</code> method which is responsible for turning (among other things) a record into its <code class="language-plaintext highlighter-rouge">id</code>, and delegating its behavior to the record.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">ApplicationDecorator</span>
  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
    <span class="vi">@record</span> <span class="o">=</span> <span class="n">record</span>
  <span class="k">end</span>

  <span class="n">delegate</span> <span class="ss">:to_param</span><span class="p">,</span> <span class="ss">to: :record</span>

  <span class="kp">private</span>

  <span class="nb">attr_reader</span> <span class="ss">:record</span>

  <span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span><span class="o">...</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">record</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="nb">method</span><span class="p">)</span>
      <span class="n">record</span><span class="p">.</span><span class="nf">public_send</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>
    <span class="k">else</span>
      <span class="k">super</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">respond_to_missing?</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">include_private</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
    <span class="n">record</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="o">||</span> <span class="k">super</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>Of course, forwarding every Rails default behaviors to the underlying record is not a great strategy (too much complexity). So, how should I do it?</p>

<h2 id="use-ruby-standard-simpledelegator">Use Ruby standard SimpleDelegator</h2>

<blockquote>
  <p><a href="https://ruby-doc.org/3.4.1/stdlibs/delegate/SimpleDelegator.html" target="_blank">SimpleDelegator</a> provides the means to delegate all supported method calls to the object passed into the constructor.</p>
</blockquote>

<p>This means that by using SimpleDelegator, I can remove the initialization and the delegation logics from my <code class="language-plaintext highlighter-rouge">ApplicationDelegator</code>.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s2">"delegate"</span>

<span class="k">class</span> <span class="nc">ApplicationDecorator</span> <span class="o">&lt;</span> <span class="no">SimpleDelegator</span> <span class="p">;</span> <span class="k">end</span></code></pre></figure>

<p>Everything is abstracted away. And it just works™. <code class="language-plaintext highlighter-rouge">@record</code> is not available anymore for my <code class="language-plaintext highlighter-rouge">TeacherDecorator</code> to reference, but SimpleDelegator exposes a <code class="language-plaintext highlighter-rouge">__getobj__</code> that works exactly as my previous <code class="language-plaintext highlighter-rouge">@record</code> ivar.</p>

<h2 id="final-implementation">Final implementation</h2>

<p>Here’s what I ended up with:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s2">"delegate"</span>

<span class="k">class</span> <span class="nc">ApplicationDecorator</span> <span class="o">&lt;</span> <span class="no">SimpleDelegator</span> <span class="p">;</span> <span class="k">end</span>

<span class="k">class</span> <span class="nc">TeacherDecorator</span> <span class="o">&lt;</span> <span class="no">ApplicationDecorator</span>
  <span class="k">def</span> <span class="nf">availability_as_background</span>
    <span class="k">case</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">max_number_of_students</span> <span class="o">&lt;=&gt;</span> <span class="n">teacher</span><span class="p">.</span><span class="nf">available_places</span>
    <span class="k">when</span> <span class="o">-</span><span class="mi">1</span> <span class="k">then</span> <span class="s2">"background-danger"</span>
    <span class="k">when</span> <span class="mi">0</span> <span class="k">then</span> <span class="s2">"background-warning"</span>
    <span class="k">when</span> <span class="mi">1</span> <span class="k">then</span> <span class="s2">"background-success"</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="kp">alias_method</span> <span class="ss">:teacher</span><span class="p">,</span> <span class="ss">:__getobj__</span>
<span class="k">end</span></code></pre></figure>

<p>That’s it! A 30-minute minimal decorator in <a href="/series/ruby">plain Ruby</a>.</p>

<p>Cheers,</p>

<p>Rémi - <a href="https://ruby.social/@remi">@remi@ruby.social</a></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><code class="language-plaintext highlighter-rouge">respond_to_missing?</code> only ensures that the <code class="language-plaintext highlighter-rouge">responds_to?</code> does not return false positives by allowing the decorator to respond to methods even if they are not statically defined on it. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Rémi Mercier</name></author><category term="ruby" /><summary type="html"><![CDATA[A while ago, I needed to add some view-related instance methods to a model. Decorators are my go-to pattern to handle this kind of logic. So, I built a minimal decorator from scratch, added a bunch of extra behaviors, only to end up abstracting all of that away. Follow along!]]></summary></entry><entry><title type="html">Speed up RSpec tests: understand lifecycle and execution</title><link href="https://remimercier.com/aggregate-rspec-expectations/" rel="alternate" type="text/html" title="Speed up RSpec tests: understand lifecycle and execution" /><published>2025-03-10T00:00:00+00:00</published><updated>2025-03-10T00:00:00+00:00</updated><id>https://remimercier.com/speed-up-rspec-suite-by-understanding-lifecycles</id><content type="html" xml:base="https://remimercier.com/aggregate-rspec-expectations/"><![CDATA[<p>One of RSpec’s strengths is the legibility of its behavior-based DSL. The other side of this coin is that the proliferation of small example blocks introduces a performance overhead. Why? Because of RSpec test files’ lifecycle! I’ll describe broadly how RSpec handles a test file, and the performance implications. Then, we’ll see how we can make your RSpec tests run faster!</p>

<p>This is an intermediate-level post. If you’re unfamiliar with RSpec, <a href="/series/rspec/">start at the beginning of the series</a>.</p>

<h2 id="a-typical-test-file">A typical test file</h2>

<p>Let’s draw a quick <code class="language-plaintext highlighter-rouge">books_controller_spec.rb</code> that checks the behavior of a <code class="language-plaintext highlighter-rouge">PATCH</code> action.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">BooksController</span> <span class="k">do</span>
    <span class="n">let</span><span class="p">(</span><span class="ss">:book</span><span class="p">)</span> <span class="k">do</span>
      <span class="no">Book</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s2">"The Long Way to a Small, Angry Planet"</span><span class="p">,</span> <span class="ss">blurb: </span><span class="kp">nil</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">describe</span> <span class="s2">"PATCH /books/:id"</span> <span class="k">do</span>
      <span class="n">let</span><span class="p">(</span><span class="ss">:action</span><span class="p">)</span> <span class="p">{</span> <span class="n">patch</span> <span class="ss">:update</span><span class="p">,</span> <span class="ss">params: </span><span class="p">}</span>
      <span class="n">let</span><span class="p">(</span><span class="ss">:params</span><span class="p">)</span> <span class="p">{</span> <span class="p">{</span> <span class="ss">id: </span><span class="n">book</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span> <span class="ss">book: </span><span class="p">{</span> <span class="ss">blurb: </span><span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
      <span class="n">let</span><span class="p">(</span><span class="ss">:blurb</span><span class="p">)</span> <span class="k">do</span>
        <span class="s2">"Fleeing her old life, Rosemary Harper joins the multi-species crew of the Wayfarer as a file clerk, and follows them on their various missions throughout the galaxy."</span>
      <span class="k">end</span>

      <span class="n">it</span> <span class="s2">"returns an :ok response"</span> <span class="k">do</span>
        <span class="n">action</span>

        <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">).</span><span class="nf">to</span> <span class="n">have_http_status</span><span class="p">(</span><span class="ss">:ok</span><span class="p">)</span>
      <span class="k">end</span>

      <span class="n">it</span> <span class="s2">"changes the blurb of the book"</span> <span class="k">do</span>
        <span class="n">action</span>

        <span class="n">expect</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="nf">reload</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">blurb</span><span class="p">)</span>
      <span class="k">end</span>

      <span class="n">it</span> <span class="s2">"redirects the user to the /show page"</span> <span class="k">do</span>
        <span class="n">action</span>

        <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">).</span><span class="nf">to</span> <span class="n">redirect_to</span><span class="p">(</span><span class="s2">"/books/</span><span class="si">#{</span><span class="n">book</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>This test file is pretty straightforward:</p>
<ul>
  <li>The setup creates a book with a title and no blurb.</li>
  <li>The tests verify that the <code class="language-plaintext highlighter-rouge">BooksController#update</code> method works as intended: update the attributes, return the correct response, and check for final redirection.</li>
</ul>

<p>One question, though. Can you tell how RSpec <strong>actually</strong> runs this test?</p>

<h2 id="the-lifecycle-of-a-test-file">The lifecycle of a test file</h2>

<p>Test files are separated into two concepts: <strong>example groups</strong> and <strong>examples</strong>.</p>

<p><strong>Example groups</strong> are a recursive entity in RSpec. They represent:</p>
<ul>
  <li>the logic within <code class="language-plaintext highlighter-rouge">RSpec.describe BooksController do</code>,</li>
  <li>the logic within <code class="language-plaintext highlighter-rouge">describe "PATCH /books/:id" do</code> or <code class="language-plaintext highlighter-rouge">context "when something is different" do</code>.</li>
</ul>

<p><strong>Examples</strong> represent the logic within <code class="language-plaintext highlighter-rouge">it</code> blocks. And <code class="language-plaintext highlighter-rouge">it</code> blocks encapsulate <strong>expectations</strong>.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">BooksController</span> <span class="k">do</span>                 <span class="c1"># &lt;-- top-level example group</span>
    <span class="n">describe</span> <span class="s2">"PATCH /books/:id"</span> <span class="k">do</span>                <span class="c1"># &lt;-- nested example group</span>
      <span class="n">it</span> <span class="s2">"returns an :ok response"</span> <span class="k">do</span>             <span class="c1"># &lt;-- example</span>
        <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">).</span><span class="nf">to</span> <span class="n">have_http_status</span><span class="p">(</span><span class="ss">:ok</span><span class="p">)</span> <span class="c1"># &lt;-- expectation</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>When running a test file, RSpec will:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">run</code> the top-level example group,</li>
  <li>within <code class="language-plaintext highlighter-rouge">run</code>, <code class="language-plaintext highlighter-rouge">run_examples</code>,</li>
  <li>if the examples are nested example groups, it will <code class="language-plaintext highlighter-rouge">run</code> recursively,</li>
  <li>until RSpec can run an actual example, i.e. an <code class="language-plaintext highlighter-rouge">it</code> block.</li>
</ul>

<p>Next, for each example (<code class="language-plaintext highlighter-rouge">it</code> blocks), RSpec does a handful of things <sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">run_before_context_hooks</code>: creates the instance variables (your <code class="language-plaintext highlighter-rouge">let</code>)  and executes <code class="language-plaintext highlighter-rouge">before</code> blocks,</li>
  <li>runs the expectations,</li>
  <li>generates the report,</li>
  <li><code class="language-plaintext highlighter-rouge">run_after_context_hooks</code>: rollbacks the database and resets the ivars.</li>
</ul>

<p>In layman’s terms, it means that for each <code class="language-plaintext highlighter-rouge">it</code>, RSpec runs anew all your <code class="language-plaintext highlighter-rouge">let</code> and <code class="language-plaintext highlighter-rouge">before</code> blocks, only to discard them all at the end of the process.</p>

<p>Let’s check our <code class="language-plaintext highlighter-rouge">book</code> instance in different <code class="language-plaintext highlighter-rouge">it</code> groups.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">it</span> <span class="s2">"returns an :ok response"</span> <span class="k">do</span>
    <span class="nb">p</span> <span class="n">book</span><span class="p">.</span><span class="nf">id</span> <span class="c1"># =&gt; 1</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s2">"changes the blurb of the book"</span> <span class="k">do</span>
    <span class="nb">p</span> <span class="n">book</span><span class="p">.</span><span class="nf">id</span> <span class="c1"># =&gt; 1</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s2">"redirects the user to the /show page"</span> <span class="k">do</span>
    <span class="nb">p</span> <span class="n">book</span><span class="p">.</span><span class="nf">id</span> <span class="c1"># =&gt; 1</span>
  <span class="k">end</span></code></pre></figure>

<p>At first, I would assume that my <code class="language-plaintext highlighter-rouge">book</code> is always the same book. And yet.</p>

<p>Let’s check the memory pointer associated with my <code class="language-plaintext highlighter-rouge">book</code>.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="n">it</span> <span class="s2">"returns an :ok response"</span> <span class="k">do</span>
    <span class="nb">p</span> <span class="n">book</span><span class="p">.</span><span class="nf">object_id</span> <span class="c1"># =&gt; 56100</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s2">"changes the blurb of the book"</span> <span class="k">do</span>
    <span class="nb">p</span> <span class="n">book</span><span class="p">.</span><span class="nf">object_id</span> <span class="c1"># =&gt; 56220</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s2">"redirects the user to the /show page"</span> <span class="k">do</span>
    <span class="nb">p</span> <span class="n">book</span><span class="p">.</span><span class="nf">object_id</span> <span class="c1"># =&gt; 56350</span>
  <span class="k">end</span></code></pre></figure>

<p>Uh, what?</p>

<p>What it tells us is that while each <code class="language-plaintext highlighter-rouge">book</code> <em>looks</em> identical at the database level (same <code class="language-plaintext highlighter-rouge">id</code>), each Ruby object we’re looking at is different (different memory pointer <code class="language-plaintext highlighter-rouge">object_id</code>).</p>

<p>So, RSpec recreates new Ruby objects for every <code class="language-plaintext highlighter-rouge">it</code> block.</p>

<p>But what about <code class="language-plaintext highlighter-rouge">book</code> always having the same <code class="language-plaintext highlighter-rouge">id</code>?</p>

<p>Most Rails integration of RSpec offers the ability to wrap examples in a database transaction <sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>, meaning that once your example has run, the database rolls back to its original state. Hence why you always have <code class="language-plaintext highlighter-rouge">book</code> with the same <code class="language-plaintext highlighter-rouge">id</code>.</p>

<p>The TL;DR is that the more <code class="language-plaintext highlighter-rouge">it</code> blocks you have, the more RSpec has to evaluate your setup, fill your test database with new records, instantiate new Ruby objects, and discard all of it.</p>

<p>While this decouples testing data from the order of test execution, for a lot of everyday tests, this is a tad overkill and slows your test suite down.</p>

<p>Ok, Rémi? So, how do we fix it?</p>

<h2 id="aggregate-examples-and-cut-setup-time">Aggregate examples and cut setup time</h2>

<p>There’s a very simple thing you can do, to cut setup time <strong>big time</strong>: aggregate your expectations in fewer examples.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">  <span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">BooksController</span> <span class="k">do</span>
    <span class="n">let</span><span class="p">(</span><span class="ss">:book</span><span class="p">)</span> <span class="k">do</span>
      <span class="no">Book</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s2">"The Long Way to a Small, Angry Planet"</span><span class="p">,</span> <span class="ss">blurb: </span><span class="kp">nil</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">describe</span> <span class="s2">"PATCH /books/:id"</span> <span class="k">do</span>
      <span class="n">let</span><span class="p">(</span><span class="ss">:action</span><span class="p">)</span> <span class="p">{</span> <span class="n">patch</span> <span class="ss">:update</span><span class="p">,</span> <span class="ss">params: </span><span class="p">}</span>
      <span class="n">let</span><span class="p">(</span><span class="ss">:params</span><span class="p">)</span> <span class="p">{</span> <span class="p">{</span> <span class="ss">id: </span><span class="n">book</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span> <span class="ss">book: </span><span class="p">{</span> <span class="ss">blurb: </span><span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
      <span class="n">let</span><span class="p">(</span><span class="ss">:blurb</span><span class="p">)</span> <span class="k">do</span>
        <span class="s2">"Fleeing her old life, Rosemary Harper joins the multi-species crew of the Wayfarer as a file clerk, and follows them on their various missions throughout the galaxy."</span>
      <span class="k">end</span>

      <span class="n">it</span> <span class="s2">"successfully updates the book"</span><span class="p">,</span> <span class="ss">:aggregate_failures</span> <span class="k">do</span>
        <span class="n">action</span>

        <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">).</span><span class="nf">to</span> <span class="n">have_http_status</span><span class="p">(</span><span class="ss">:ok</span><span class="p">)</span>
        <span class="n">expect</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="nf">reload</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">blurb</span><span class="p">)</span>
        <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">).</span><span class="nf">to</span> <span class="n">redirect_to</span><span class="p">(</span><span class="s2">"/books/</span><span class="si">#{</span><span class="n">book</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>Sure, you lose <em>some</em> legibility, but instead of having RSpec build your test setup three times, you only do it once. I’ll try and post an update with some benchmarks later on, but this seems quite a big gain especially when used across a whole test suite.</p>

<p>Note that I added the <code class="language-plaintext highlighter-rouge">:aggregate_failures</code> flag to my <code class="language-plaintext highlighter-rouge">it</code> block. This tells RSpec to not fail fast, to run all my expectations in the block, and to bundle all my failures together.</p>

<p>That’s it! Hope you liked this lengthy yack shaving, as much as I liked writing it!</p>

<p>Cheers,</p>

<p>Rémi - <a href="https://ruby.social/@remi">@remi@ruby.social</a></p>

<p>PSS: Many thanks to <a href="https://boitam.eu/@sunny" target="_blank">Sunny</a> for sending me down this rabbit hole!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>You can check the code about <code class="language-plaintext highlighter-rouge">run</code> <a href="https://github.com/rspec/rspec/blob/main/rspec-core/lib/rspec/core/example_group.rb#L599" target="_blank">here</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>The code about database migration is <a href="https://github.com/rspec/rspec-rails/blob/main/lib/rspec/rails/fixture_support.rb" target="_blank">here</a>. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Rémi Mercier</name></author><category term="rspec" /><summary type="html"><![CDATA[One of RSpec's strengths is the legibility of its behavior-based DSL. The other side of this coin is that the proliferation of small example blocks introduces a performance overhead.]]></summary></entry></feed>