Fork me on GitHub

Kernow Soul

Ruby, Rails and JavaScript Consultant

DRYing Up Multiple User Contexts With Shoulda Macros

| Comments

Today I’ve been writing tests for a legacy Rails application I inherited recently. The application has several user roles, each role having varying permissions. To deal with this nicely I setup shoulda macro’s to create contexts for each of the user roles, public user, standard user, admin user etc. then in my tests I could write…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public_context do

  context "on GET to :index" do
    setup do
      get :index
    end

    should_redirect_to("root url") { root_url }
  end
end

signed_in_user_context do

  context "on GET to :index" do
    setup do
      get :index
    end

    should_redirect_to("user url") { user_url }
  end
end

This is pretty standard practice now and something I picked up from looking at the code produced by the guys at Thought Bot. While working on the test suite it became apparent many of the methods behaved in the same way for multiple user roles. I wanted to come up with a way to run a group of tests under multiple user roles without having to duplicate any code. Shoulda macros to the rescue again! After creating another macro to deal with multiple contexts I can write my tests like this…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
multiple_contexts 'public_context', 'signed_in_user_context' do

  context "on GET to :show" do
    setup do
      @advert = Factory(:advert)
      get :show, :id => @advert.to_param
    end

    should_render_with_layout :application
    should_render_template :show
    should_not_set_the_flash
    should_assign_to( :advert ) { @advert }
    should_respond_with :success
  end
end

And the shoulda macro code itself…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def multiple_contexts(*contexts, &blk)
  contexts.each { |context|
    send(context, &blk) if respond_to?(context)
  }
end

def public_context(&blk)
  context "The public" do
    setup { sign_out }
    merge_block(&blk)
  end
end

def signed_in_user_context(&blk)
  context "A signed in user" do
    setup do
      @user = Factory(:user)
      sign_in_as @user
    end
    merge_block(&blk)
  end
end

Comments