Nick Sieger: Customizing RSpec http://blog.nicksieger.com/articles/2007/01/02/customizing-rspec en-us 40 "Customizing RSpec" by Martin <p>Great post &#45; thanks Nick&#46;</p> Tue, 06 Mar 2007 18:07:48 +0000 urn:uuid:dc63ac2a-ed7f-4e2e-b72e-a3216c25fe79 http://blog.nicksieger.com/articles/2007/01/02/customizing-rspec#comment-218 "Customizing RSpec" by Michal Kwiatkowski <p>I&#8217;m using RSpec in my svntl project (<a href='http://code' rel="nofollow">http://code</a>&#46;google&#46;com/p/svntl/) and had the same need to extend my contexts with custom methods&#46; All I&#8217;ve done though was extending module Spec::Runner::ContextEval::ModuleMethods with methods (as I&#8217;ve described in this snippet: <a href='http://www' rel="nofollow">http://www</a>&#46;bigbold&#46;com/snippets/posts/show/3210)&#46;</p> <p>Your recipe solves the general problem, but most of the time you won&#8217;t need anything more than this simple straightfoward solution&#46;</p> Fri, 19 Jan 2007 20:40:20 +0000 urn:uuid:8d162cc9-12ae-44e7-8154-84ea0cfe4a97 http://blog.nicksieger.com/articles/2007/01/02/customizing-rspec#comment-198 Customizing RSpec <p><em>Update/Disclaimer: I refer to parts of RSpec that are not blessed as an extension API&#46; Redefining <code>before_context_eval</code> and using the <code>@context_eval_module</code> variable directly may change in the future&#46; I&#8217;ll keep this article updated to coincide with the changes&#46; For now, these techniques should work fine with RSpec versions up to 0&#46;7&#46;5&#46;</em></p> <p>RSpec seems to be getting more attention lately as a viable, nay, <em>preferred</em>, alternative to Test::Unit&#46; It&#8217;s possible that it&#8217;s just my personal feed&#45;reader&#45;echo&#45;chamber, but consider this: <a href="http://blog.fallingsnow.net/rubinius/">Rubinius</a> has started using RSpec alongside Test::Unit as an another way to test the alternate Ruby implementation&#46; They&#8217;re even in the midst of building some snazzy extensions to allow the same specs to be run under a Ruby implementation of your choice&#46; (Perhaps this will point the way to a new round of executable specs to accompany the <a href="http://www.headius.com/rubyspec/index.php/Main_Page">fledgling community spec</a>? Let&#8217;s wait and see how they do and leave that topic for another day&#46;)</p> <p>But extending and customizing RSpec to add a DSL on top of RSpec&#8217;s <code>context/specify</code> framework doesn&#8217;t have to be the realm of experts&#46; Here are some templates for how you can DRY up your specs by adding your own helper methods in such a way that they will be available to all your specs&#46; But first, a little background&#46;</p> <h3>Spec Helper</h3> <p>Most usages of RSpec that I&#8217;ve seen in the wild use a &#8220;spec helper&#8221; (<code>spec_helper.rb</code>)&#46; This file, following the pattern of Rails&#8217; <code>test_helper.rb</code>, minimally contains require statements to pull in the RSpec code and any supporting code for running specs&#46; By requiring the spec helper via a path relative to your spec (usually with <code>require File.dirname(__FILE__) + '/spec_helper'</code> or similar), it also allows you the convenience of running your specs one at a time from anywhere (say, by launching from your editor) or with <code>rake</code> or <code>spec</code>&#46; This file is where your shared helper methods will go, and where they&#8217;ll get registered to be pulled into the contexts&#46;</p> <h3>What Context in <code>context</code>?</h3> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">context</span> <span class="punct">&quot;</span><span class="string">A new stack</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="comment"># &lt;== What is the value of &quot;self&quot; here?</span> <span class="ident">specify</span> <span class="punct">&quot;</span><span class="string">should be empty</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p>How do those contexts work anyway? The <code>context</code> method that defines a context in which specs can be defined and run takes a block to define the individual specs, but what can really go in that block?</p> <p>It turns out that RSpec jumps through metaprogramming hoops (using <code>class_eval</code>) to make the block behave like a class definition&#46; This means you can do things like put method definitions inside your context:</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">context</span> <span class="punct">&quot;</span><span class="string">A new stack</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="keyword">def </span><span class="method">a_new_stack</span> <span class="constant">Stack</span><span class="punct">.</span><span class="ident">new</span> <span class="keyword">end</span> <span class="ident">specify</span> <span class="punct">&quot;</span><span class="string">should be empty</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="ident">a_new_stack</span><span class="punct">.</span><span class="ident">should_be_empty</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p>Which is nice, but the reason we&#8217;re here is to hide that away in <code>spec_helper.rb</code>&#46; So, to get back to the point of the comment in the first example above, the <code>self</code> inside the context block is an anonymous <code>Module</code> object&#46; It&#8217;s constructed in the <code>initialize</code> method of a <code>Context</code> (condensed from <code>spec/runner/context.rb</code> in the RSpec codebase):</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Spec::Runner::Context</span> <span class="keyword">def </span><span class="method">initialize</span><span class="punct">(</span><span class="ident">name</span><span class="punct">,</span> <span class="punct">&amp;</span><span class="ident">context_block</span><span class="punct">)</span> <span class="attribute">@name</span> <span class="punct">=</span> <span class="ident">name</span> <span class="attribute">@context_eval_module</span> <span class="punct">=</span> <span class="constant">Module</span><span class="punct">.</span><span class="ident">new</span> <span class="attribute">@context_eval_module</span><span class="punct">.</span><span class="ident">extend</span> <span class="constant">ContextEval</span><span class="punct">::</span><span class="constant">ModuleMethods</span> <span class="attribute">@context_eval_module</span><span class="punct">.</span><span class="ident">include</span> <span class="constant">ContextEval</span><span class="punct">::</span><span class="constant">InstanceMethods</span> <span class="ident">before_context_eval</span> <span class="attribute">@context_eval_module</span><span class="punct">.</span><span class="ident">class_eval</span><span class="punct">(&amp;</span><span class="ident">context_block</span><span class="punct">)</span> <span class="keyword">end</span> <span class="keyword">def </span><span class="method">before_context_eval</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p>(Take note of that empty <code>before_context_eval</code> method and the fact that it&#8217;s invoked during context initialization; that&#8217;s where we can plug in our custom extensions&#46;)</p> <p>The object held by the <code>@context_eval_module</code> instance variable is being augmented in two ways: extension and inclusion&#46; The object is <em>extended</em> with the <code>ContextEval::ModuleMethods</code> module; these methods are being added to the object&#8217;s singleton class&#46; This has the effect of making these methods visible within the <code>context</code> block, functioning similar to &#8220;class&#8221; methods&#46;</p> <p>The object also has the <code>ContextEval::InstanceMethods</code> module <em>included</em>&#46; This has the effect of adding these as instance methods, making them visible from within <code>specify</code> blocks, which are made to behave like instance methods on the same object&#46;</p> <h3>Putting it together</h3> <table style="text-align: left;" summary=""> <thead> <th>Technique</th> <th>Visibility</th> <th>Use</th> </thead> <tbody> <tr><td>@context_eval_module&#46;extend</td><td>Context block</td><td>Custom setup, shared state declaration</td></tr> <tr><td>@context_eval_module&#46;include</td><td>Specify block</td><td>Shared actions/functions, stub/expectation modification, encapsulate instance variables</td></tr> </tbody> </table> <h4>Adding specialized setup methods</h4> <p><code>spec_helper.rb</code> snippet:</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">module </span><span class="module">SharedSetupMethods</span> <span class="keyword">def </span><span class="method">setup_new_stack</span> <span class="ident">setup</span> <span class="keyword">do</span> <span class="attribute">@stack</span> <span class="punct">=</span> <span class="constant">Stack</span><span class="punct">.</span><span class="ident">new</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="keyword">class </span><span class="class">Spec::Runner::Context</span> <span class="keyword">def </span><span class="method">before_context_eval</span> <span class="attribute">@context_eval_module</span><span class="punct">.</span><span class="ident">extend</span> <span class="constant">SharedSetupMethods</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p>Example spec:</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">context</span> <span class="punct">&quot;</span><span class="string">A new stack</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="ident">setup_new_stack</span> <span class="ident">specify</span> <span class="punct">&quot;</span><span class="string">should be empty</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="attribute">@stack</span><span class="punct">.</span><span class="ident">should_be_empty</span> <span class="keyword">end</span> <span class="keyword">end</span> </code></pre></div> <h4>Adding shared accessors</h4> <p><code>spec_helper.rb</code> snippet:</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">module </span><span class="module">StackMethods</span> <span class="ident">attr_accessor</span> <span class="symbol">:stack</span> <span class="keyword">def </span><span class="method">push_an_object</span> <span class="ident">stack</span> <span class="punct">&lt;&lt;</span> <span class="ident">mock</span><span class="punct">(&quot;</span><span class="string">some object</span><span class="punct">&quot;)</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="keyword">class </span><span class="class">Spec::Runner::Context</span> <span class="keyword">def </span><span class="method">before_context_eval</span> <span class="attribute">@context_eval_module</span><span class="punct">.</span><span class="ident">include</span> <span class="constant">StackMethods</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p>Example spec:</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">context</span> <span class="punct">&quot;</span><span class="string">A stack with an object pushed</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="ident">setup</span> <span class="keyword">do</span> <span class="attribute">@stack</span> <span class="punct">=</span> <span class="constant">Stack</span><span class="punct">.</span><span class="ident">new</span> <span class="keyword">end</span> <span class="ident">specify</span> <span class="punct">&quot;</span><span class="string">should not be empty</span><span class="punct">&quot;</span> <span class="keyword">do</span> <span class="ident">stack</span><span class="punct">.</span><span class="ident">should_be_empty</span> <span class="ident">push_an_object</span> <span class="ident">stack</span><span class="punct">.</span><span class="ident">should_not_be_empty</span> <span class="keyword">end</span> <span class="keyword">end</span> </code></pre></div> <p>The examples are simple, but hopefully illustrate the techniques&#46; For an example of some code that&#8217;s actually useful, check out my sample <a href="http://svn.caldersphere.net/svn/main/rspec_selenium_rc/trunk/">RSpec Selenium RC integration project</a>, in particular the <a href="http://svn.caldersphere.net/svn/main/rspec_selenium_rc/trunk/lib/spec_helper.rb">spec helper</a> and the <a href="http://svn.caldersphere.net/svn/main/rspec_selenium_rc/trunk/spec/example_spec.rb">example spec</a>&#46; (More on this in the future if it proves useful, but for now if you check it out and run <code>rake</code> on it, it should launch <a href="http://www.openqa.org/selenium-rc/">Selenium RC</a> and run the example spec in a Firefox browser&#46;)</p> <p>By mixing and matching these techniques, you can layer a mini&#45;DSL on top of RSpec and achieve DRY&#45;er and even more readable and intention&#45;revealing specs&#46; Let me know if you&#8217;re able to find uses for these tips!</p> Tue, 02 Jan 2007 05:32:00 +0000 urn:uuid:21755a2d-0ffa-4f0c-be7d-a2e2e01d99b4 Nick Sieger http://blog.nicksieger.com/articles/2007/01/02/customizing-rspec rspec ruby metaprogramming http://blog.nicksieger.com/articles/trackback/178