I am involved in a personal project where I am developing a web application with Rails. I am using RSpec and Cucumber, and I am following an outside-in, behavior-driven development approach, as recommended in the excellent RSpec Book. In this post, I would like to share a problem I found and how I solved it.

It started when I tried to refactor some duplicated code in my controller’s specs. It was related to the authentication system. Specs should test their behavior in both authenticated and insecure contexts, to be sure that anything happens if an anonymous user sends a POST action to your WorldDestroyerController. For example:

describe UserSessionsController, "DELETE destroy" do
  should_require_login :delete, :destroy

  context "authenticated user" do
    before(:each) do
      login_as_user
    end

    it "should destroy the user session" do
      @current_user_session.should_receive(:destroy)
      delete :destroy
    end

    it "should redirect to the login page" do
      delete :destroy
      response.should redirect_to login_path
    end  
  end
 end

The next fragment was recurrent in all my examples.

context "authenticated user" do
    before(:each) do
        login_as_user
    end

For every example in the example group, it basically stubs the controller method that validates sessions in order to get a valid one when invoked (I am using authlogic). I wanted to create a macro context_authenticated so I could code the previous example like this:

describe UserSessionsController, "DELETE destroy" do
  should_require_login :delete, :destroy
  context_authenticated do
    it "should destroy the user session" do
      @current_user_session.should_receive(:destroy)
      delete :destroy
    end

    it "should redirect to the login page" do
      delete :destroy
      response.should redirect_to login_path
    end  
  end
end

My first attempt was to write a macro that included the before the block and yielded to the block provided in the invocation:

def context_authenticated
    context "authenticated user" do
        before(:each) do
            login_as_user
        end
        yield #Wrong
    end
end

The examples failed because the before(:each) fragment was not being invoked before executing them. To discover the problem I had to investigate a little bit about how the RSpec DSL works. Basically:

  • Each context (alias for describe) creates a new ExampleGroup subclass.
  • Each it (alias for example) creates a new instance of the current ExampleGroup subclass where it is invoked.

So the problem was related to the very nature of closures. In Ruby, a block of code (proc or lambda) maintains the bindings in effect for that closure. The bindings contain not only variable references but also the self reference itself.

In my case, what I was doing was to submit a closure with the it examples to the macro. The execution context of this closure was the one where it was defined: the ExampleGroup subclass created by describe UserSessionsController.... However, inside the macro, a new ExampleGroup subclass was created, and the before block was associated with that example group. That was the reason why the the before code was not getting executed before the submitted examples. Once I understood this, the solution was simple:

def context_authenticated(&example_group_block)
    example_group_class = context "authenticated user" do
        before(:each) do
            login_as_user
        end
    end
    example_group_class.class_eval &example_group_block
end

What the previous code does is to change the evaluation context of the closure, so it gets executed inside the ExampleGroup class created by the macro.