« HE:labs
HE:labs

How do I test an application_controller on a rails app

Postado por Mauro George em 31/01/2014

Have you ever in your life as a rails developer needed to test an application controller? How did you do that? Let's take a look at the dos and dontdos.

A simple application_controller on a rails app

I will use the strawberrycake of Flavia, as an example. It is a simple app that uses a login via facebook. Lets take a look at the application_controller.rb.

 1 class ApplicationController < ActionController::Base
 2   protect_from_forgery
 3   ensure_security_headers # See more: https://github.com/twitter/secureheaders
 4   helper_method :current_user, :user_signed_in?
 5 
 6   private
 7 
 8   def current_user
 9     @current_user ||= User.find(session[:user_id]) if session[:user_id]
10     rescue ActiveRecord::RecordNotFound
11       session.delete(:user_id)
12       nil
13   end
14 
15   def user_signed_in?
16     !current_user.nil?
17   end
18 
19   def authenticate!
20     user_signed_in? || redirect_to(root_url, notice: "Você precisa estar autenticado...")
21   end
22 end

We have 3 private methods, that we will use in our controllers like a posts_controller. Lets take a look at the specs.

 1 require 'spec_helper'
 2 
 3 describe ApplicationController do
 4   let!(:user) { create(:user) }
 5 
 6   # ...
 7 
 8   describe "user_signed_in? helper" do
 9     context "with user logged in" do
10       before do
11         session[:user_id] = user.id
12       end
13 
14       it "returns true" do
15         expect(controller.send(:user_signed_in?)).to be_true
16       end
17     end
18 
19     context "without user logged in" do
20       it "returns false" do
21         expect(controller.send(:user_signed_in?)).to be_false
22       end
23     end
24   end
25 end

I showed only a few lines, but for our example it is ok. We need to focus on how the tests are made. They are using controller.send to access a private method. It is a good practice to not test private methods. A private method is an implementation detail that should be hidden to the users of the class. If our private method has big responsibility and doing a lot of stuffs, it is better to extract this private methods to its own class. Let's refactor these specs!

Meet the anonymous controller

The RSpec Rails have the anonymous controller it is a nice way to test the application_controller. Let's use it.

The current_user

The objective of the current_user method it is to return the current user if it is present, or to return nil if we don't have a current user. Let's change the specs to use the anonymous controller.

 1 describe ApplicationController do
 2 
 3   controller do
 4 
 5     def index
 6       @current_user = current_user
 7       render text: 'Hello World'
 8     end
 9   end
10 
11   let!(:user) do
12     create(:user)
13   end
14 
15   describe '#current_user' do
16 
17     context 'with user logged in' do
18 
19       before do
20         sign_in_via_facebook(user)
21         get :index
22       end
23 
24       it 'assigns the current_user' do
25         expect(assigns(:current_user)).to eq(user)
26       end
27     end
28 
29     context 'without user logged in' do
30 
31       it 'current_user be nil' do
32         get :index
33         expect(assigns(:current_user)).to be_nil
34       end
35     end
36 
37     context "can't find the user" do
38 
39       before do
40         session[:user_id] = '#77'
41         get :index
42       end
43 
44       it 'current_user be nil' do
45         expect(assigns(:current_user)).to be_nil
46       end
47 
48       it 'unsets the session[:user_id]' do
49         expect(session[:user_id]).to be_nil
50       end
51     end
52   end
53 end

First we create a controller using the block controller, with this we can create an anonymous controller that behaves like a regular controller inherited from ApplicationController. In this controller we create a single action that assigns @current_user with the value of current_user.

Now we can test index action the way we test all regular actions. We don't need the send method. We just need to test the value of @current_user only.

The user_signed_in?

The same way we create an index action, we can create a new action. The anonymous controller have all the resource routes. If you try a custom route on this controller you get an ActionController::RoutingError.

 1 describe ApplicationController do
 2 
 3   controller do
 4 
 5     def index
 6       @current_user = current_user
 7       render text: 'Hello World'
 8     end
 9 
10     def show
11       if user_signed_in?
12         render text: 'Signed user'
13       else
14         render text: 'Not signed user'
15       end
16     end
17   end
18 
19   let!(:user) do
20     create(:user)
21   end
22 
23   # ...
24 
25   describe '#user_signed_in?' do
26 
27     context 'with user logged in' do
28 
29       before do
30         sign_in_via_facebook(user)
31         get :show, id: 2
32       end
33 
34       it 'be a signed user' do
35         expect(response.body).to include('Signed user')
36       end
37     end
38 
39     context 'without user logged in' do
40 
41       it 'returns false' do
42         get :show, id: 2
43         expect(response.body).to include('Not signed user')
44       end
45     end
46   end
47 end

To test the user_signed_in? we create a show action. This action shows a text based state of user, if it's logged in or not. This way we can simply test the response, as a regular controller.

The last one is the authenticate!

To finish, we create an action new on our controller. If you still have a doubt if the controller block is a controller, you should add a before_filter in the new action.

 1 describe ApplicationController do
 2 
 3   controller do
 4     before_filter :authenticate!, only: [:new]
 5 
 6     def index
 7       @current_user = current_user
 8       render text: 'Hello World'
 9     end
10 
11     def show
12       if user_signed_in?
13         render text: 'Signed user'
14       else
15         render text: 'Not signed user'
16       end
17     end
18 
19     def new
20       render text: 'A new thing'
21     end
22   end
23 
24   # ...
25 
26   describe '#authenticate!' do
27 
28     include_examples "authentication required" do
29       let(:action) { get :new }
30     end
31 
32     context "logged in" do
33 
34       before do
35         sign_in_via_facebook(user)
36         get :new
37       end
38 
39       it { should respond_with(:success) }
40     end
41   end
42 end

We use the actual shared example of authentication and make a simple test that the action answers with success when user is logged in.

Conclusion

Using anonymous controller we can make our tests on the application controller without the need to use send. This way we can keep our specs testing the behavior and not the implementation details.

Keep hacking!

Compartilhe

Comentários