before_action/after_action

November 6th, 2007

A while back there was either a feature request in the rspec tracker, or a suggestion on one of the rspec mailing lists, for a feature to DRY up controller specs.

The idea was that this pattern feels a bit clunky:

describe PersonController, "handling failed POST to create" do
  def do_post
    post :create, invalid_arguments
  end

it "should redisplay the create form" do do_post response.should render_template("people/new") end

it "should try to create a Person" do Person.should_receive(:create).with(invalid_arguments).and_return(false) do_post end end

And it would be nice to have something that was more expressive using tags like this:

describe PersonController, "handling failed POST to create" do
  def do_post
    post :create, invalid_arguments
  end

it "should redisplay the create form", :after => :do_post do response.should render_template("people/new") end

it "should try to create a Person", :before => :do_post do Person.should_receive(:create).with(invalid_arguments).and_return(false) end end

I didn’t add this to rspec_on_rails because I personally find it harder to read. It also doesn’t support situations where you want to stub something before the action and then set a state-based expecation after the action.

But the problem is still present, and it would be nice to have something a bit less clunky.

Well – here’s what I’ve been experimenting with. This is NOT part of RSpec, and I may not want to include it in RSpec because I think it’s somewhat particular to my personal style (as opposed to a style that I think is “right” for BDD), but it’s easy enough to add to your own projects.

Here’s what the specs look like:

describe PersonController, "handling failed POST to create" do
  def do_post
    post :create, invalid_arguments
  end

it "should redisplay the create form" do after_post do response.should render_template("people/new") end end

it "should try to create a Person" do during_post do Person.should_receive(:create).with(invalid_arguments).and_return(false) end end end

I really like this even though it actually turns out to be a bit more verbose. I think it speaks very clearly about what is going on – especially “during_post”, which describes very well when the Person.should_receive the :create message.

Here’s the code in spec_helper.rb that supports this pattern:

[:get, :post, :put, :delete, :render].each do |action|
  eval %Q{
    def before_#{action}
      yield
      do_#{action}
    end
    alias during_#{action} before_#{action}
    def after_#{action}
      do_#{action}
      yield
    end
  }
end

This supports controller and view specs (hence including :render).

Please try it out and let me know what you think.

11 Responses to “before_action/after_action”

  1. Craig Buchek Says:

    I agree that the original pattern feels clunky, and that we should do something to fix it. And the :before => do_post syntax isn’t clear whether the do_post runs before the spec, or if the spec should run before the do_post.

    <p>At a basic level, what we want is to add a before clause (that runs before the standard before clause) that is specific to an individual spec. So, I&#8217;d prefer it to look something like this:</p>
    

    
    describe PersonController, "handling failed POST to create" do
      before(:each)
        post :create, invalid_arguments
      end

    it "should redisplay the create form" do response.should render_template("people/new") end

    it "should try to create a Person" do before_the_before_clause do Person.should_receive(:create).with(invalid_arguments).and_return(false) end end end

    I’m not sure how hard it would be to implement that though. I would guess that it would require a serious architectural change to RSpec.

    <p>I think the case mainly arises due to the way we do mocking. My suggestion would be to do the stubbing in the before clause, and separate the mock assertions, moving them into the specs:</p>
    

    
    describe PersonController, "handling failed POST to create" do
      before(:each)
        Person.upon_receiving(:create).with(invalid_arguments).should_return(false)
        post :create, invalid_arguments
      end

    it "should redisplay the create form" do response.should render_template("people/new") end

    it "should try to create a Person" do Person.should_have_received(:create).with(invalid_arguments) end end

    I understand that we’d probably have to re-write a lot of the mocking and stubbing code, but I think that this expresses intent a lot better, and probably solves the root of the problem you’re trying to solve. If something like this exists, I’d be interested in learning about it. If not, I’d be willing to help make it come about.

  2. Daniel Fischer Says:

    I actually like the verbosity of this style; this is definitely something I would adopt. I’d actually like to see this implemented into RSpec. I’ll ask around and see what others have to say. So far, I like it!

  3. nicholas a. evans Says:

    I think I might have been the one to originally suggest the tags version. I like your version better. :-) I’m going to start trying it out.

    <p>I like the distinction between before/during.  Even though they are the &#8220;same&#8221; thing from a coding standpoint, they represent subtly different ideas from a &#8220;why do I care&#8221; standpoint.  It reminds me of Brian Marick&#8217;s recent mock-related posts: http://www.exampler.com/blog/2007/10/02/code-sample/&lt;/p&gt;
    
    
    <p>@Craig: I&#8217;m interested in something similar to your second example.  should_have_received seems like a nice addition to the mock library.</p>
    
  4. David Chelimsky Says:

    @craig & @nicholas:

    <p>I like the should_have_received idea in conjunction with upon_receipt_of or some such. The idea being that they <span class="caps">MUST</span> be a pair &#8211; you can&#8217;t just say retroactively should_have_received. The reason is that you need to set up how the object will respond before the action occurs. So upon_receipt_of would be like an optionally-verifiable stub, w/ should_have_received being the optional verification.</p>
    
    
    <p><span class="caps">WDYT</span>?</p>
    
  5. nicholas a. evans Says:

    @david:

    <p>That sounds reasonable to me.  Would there be any functional difference between &#8220;foo.upon_receipt_of(:bar)&#8221; and &#8220;foo.should_receive(:bar).any_number_of_times&#8221;?  If not, it seems to me that &#8220;any_number_of_times becomes&#8221; unnecessary.</p>
    
    
    <p>how about using upon_receiving instead of upon_receipt_of.  I dunno why&#8230; it just feels simpler to me.</p>
    
    
    <p>One of my major usage patterns now is to override a stub (in the before) with a mock expectation in one of the example blocks.  Unfortunately, rspec stubs don&#8217;t allow as fine-grained expectations, and rspec mocks aren&#8217;t overridden very easily.  upon_receiving/should_have_received could probably replace that pattern most of the time.</p>
    
    
    <p>My only nitpick is that the upon_receiving/should_have_received pair doesn&#8217;t feel as <span class="caps">DRY</span> as I&#8217;d like it to&#8230; but I can tolerate slightly verbose <span class="caps">DAMP</span> tests, and I can&#8217;t think of a better suggestion at the moment.</p>
    
  6. Craig Buchek Says:

    @David:

    <p>Yes, the mock check (should_have_received) would require that the stub (upon_receipt_of) had been set up before the call to the method in question. The stub part would set up monitoring/recording of all calls it receives, and the mock check would review those calls.</p>
    
    
    <p>I&#8217;ve not looked into the recording-type mocking libraries (in other languages), but I suppose they might be similar to what I&#8217;m looking for.</p>
    
    
    <p>Thinking about it some more, I think it might not be all that hard to implement. There&#8217;s already implicit mock checking after each spec is run, so doing the same thing with explicit checks shouldn&#8217;t be too hard.</p>
    
  7. Craig Buchek Says:

    @nicholas:

    <p>There&#8217;s a significant difference between should_receive and upon_receiving&#8212;should_receive asserts that the method <span class="caps">MUST</span> be called, where upon_receiving only says what to do IF the method is called.</p>
    
    
    <p>The any_number_of_times would be added to the bottom should_have_received part. Basically, you default the number of times that the method should have been called to either 1 or &#8220;any&#8221;, and add any_number_of_times or x_number_of_times to change the default.</p>
    
    
    <p>It might feel more <span class="caps">DRY</span> to you if you omit the with() clause in the stub/setup. Unless you want to return different values for different calls, there&#8217;s no point in adding the with() part. And in the mock check at the bottom, you could also omit it, if you don&#8217;t care about what arguments were sent.</p>
    
    
    <p>I agree that it doesn&#8217;t feel as <span class="caps">DRY</span> as it could, duplicating the callee and the method name, and perhaps the arguments. But that&#8217;s because you&#8217;re doing 2 different things, and if you <span class="caps">DRY</span> it out by combining the 2 things, you end up in the situation that lead to the bad pattern that David first described.</p>
    
  8. nicholas a. evans Says:

    craig: @should_receive(:foo).any_number_of_times doesn’t actually make any assertions. any means zero or more. Obviously, it reads a bit oddly, because it looks like it is making an assertion, but for all practical purposes it isn’t. My only use case for should_receive(:foo).any_number_of_times is when stub!(:foo) isn’t powerful enough to get the job done.

    <p>My question is, does <code>upon_receiving(:foo)</code> need to do anything more (or less)
    

    than should_receive(:foo).any_number_of_times currently does? If not, then upon_receiving(:foo) should be a drop-in replacement, and the clunkier any_number_of_times should be deprecated (IMHO).

    <p>As for the <span class="caps">DRY</span> concerns&#8230; I think I&#8217;d rather work with a while before making
    

    any strong suggestions… but I was thinking about something like the following:

    
    describe PersonController, "handling failed POST to create" do
      before(:each)
        @expectation = Person.upon_receiving(:create).
                              with(invalid_arguments).
                              should_return(false)
        post :create, invalid_arguments
      end

    it "should redisplay the create form" do response.should render_template("people/new") end

    it "should try to create a Person" do @expectation.should_have_received(:create) end end

    But does that read well?

  9. Craig Buchek Says:

    @nicholas:

    <p>Sorry, I forgot that any_number_of_times includes 0 times. So yes, I suppose it&#8217;s what I&#8217;m looking for, at least as long as I can check later to see if it was called, how many times, and with what parameters.</p>
    
    
    <p>I don&#8217;t think the assignment to @expectation works well, at least not with the current wording. (I really do like the idea though.) It&#8217;s not the expectation that should_have_received the method call, so it doesn&#8217;t quite sound right.</p>
    
    
    <p>I&#8217;m also starting to think that should_return is poorly worded. With the word &#8220;should&#8221;, it sounds like an assertion. Perhaps will_return, shall_return, or returns. Also note that in the setup, with(arguments) is a filter, so that only calls with those arguments passed are stubbed. More likely, you&#8217;d want to specify it in the spec, where it would be an assertion that the parameters were passed in the call.</p>
    
  10. nicholas a. evans Says:

    @craig:

    <p>I agree with you on the wording.  <code>@expectation</code> would need to have a different
    

    method call than should_have_received. It doesn’t need the method name to be reiterated either. But I can’t come up with anything better at the moment… Oh well. Also, you are right that should_return doesn’t match up well with upon_receiving. I like will_return the best, there.

    <p>I don&#8217;t know whether using <code>with(arguments)</code> as a filter in the <code>before</code> or as
    

    an assertion in the spec is more likely. I do it both ways, depending on the situation. So I’d like both to be supported.

  11. Michael Klishin Says:

    David,

    <p>I&#8217;m actually using the same approach you suggested to think about. Helper methods with blocks are helpful for exactly this purpose.</p>
    
    
    <p>Go for it I should say.</p>