« 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ApplicationController < ActionController::Base
  protect_from_forgery
  ensure_security_headers # See more: https://github.com/twitter/secureheaders
  helper_method :current_user, :user_signed_in?

private

def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] rescue ActiveRecord::RecordNotFound session.delete(:user_id) nil end

def user_signed_in? !current_user.nil? end

def authenticate! user_signed_in? || redirect_to(root_url, notice: "Você precisa estar autenticado...") end 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'spec_helper'

describe ApplicationController do let!(:user) { create(:user) }

# ...

describe "user_signed_in? helper" do context "with user logged in" do before do session[:user_id] = user.id end

  <span class="n">it</span> <span class="s2">"returns true"</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">controller</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:user_signed_in?</span><span class="p">)).</span><span class="nf">to</span> <span class="n">be_true</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">context</span> <span class="s2">"without user logged in"</span> <span class="k">do</span>
  <span class="n">it</span> <span class="s2">"returns false"</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">controller</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:user_signed_in?</span><span class="p">)).</span><span class="nf">to</span> <span class="n">be_false</span>
  <span class="k">end</span>
<span class="k">end</span>

end 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
describe ApplicationController do

controller do

<span class="k">def</span> <span class="nf">index</span>
  <span class="vi">@current_user</span> <span class="o">=</span> <span class="n">current_user</span>
  <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Hello World'</span>
<span class="k">end</span>

end

let!(:user) do create(:user) end

describe '#current_user' do

<span class="n">context</span> <span class="s1">'with user logged in'</span> <span class="k">do</span>

  <span class="n">before</span> <span class="k">do</span>
    <span class="n">sign_in_via_facebook</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
    <span class="n">get</span> <span class="ss">:index</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s1">'assigns the current_user'</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">assigns</span><span class="p">(</span><span class="ss">:current_user</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">context</span> <span class="s1">'without user logged in'</span> <span class="k">do</span>

  <span class="n">it</span> <span class="s1">'current_user be nil'</span> <span class="k">do</span>
    <span class="n">get</span> <span class="ss">:index</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">assigns</span><span class="p">(</span><span class="ss">:current_user</span><span class="p">)).</span><span class="nf">to</span> <span class="n">be_nil</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">context</span> <span class="s2">"can't find the user"</span> <span class="k">do</span>

  <span class="n">before</span> <span class="k">do</span>
    <span class="n">session</span><span class="p">[</span><span class="ss">:user_id</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'#77'</span>
    <span class="n">get</span> <span class="ss">:index</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s1">'current_user be nil'</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">assigns</span><span class="p">(</span><span class="ss">:current_user</span><span class="p">)).</span><span class="nf">to</span> <span class="n">be_nil</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s1">'unsets the session[:user_id]'</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">session</span><span class="p">[</span><span class="ss">:user_id</span><span class="p">]).</span><span class="nf">to</span> <span class="n">be_nil</span>
  <span class="k">end</span>
<span class="k">end</span>

end 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
describe ApplicationController do

controller do

<span class="k">def</span> <span class="nf">index</span>
  <span class="vi">@current_user</span> <span class="o">=</span> <span class="n">current_user</span>
  <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Hello World'</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">show</span>
  <span class="k">if</span> <span class="n">user_signed_in?</span>
    <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Signed user'</span>
  <span class="k">else</span>
    <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Not signed user'</span>
  <span class="k">end</span>
<span class="k">end</span>

end

let!(:user) do create(:user) end

# ...

describe '#user_signed_in?' do

<span class="n">context</span> <span class="s1">'with user logged in'</span> <span class="k">do</span>

  <span class="n">before</span> <span class="k">do</span>
    <span class="n">sign_in_via_facebook</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
    <span class="n">get</span> <span class="ss">:show</span><span class="p">,</span> <span class="ss">id: </span><span class="mi">2</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="s1">'be a signed user'</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">).</span><span class="nf">to</span> <span class="kp">include</span><span class="p">(</span><span class="s1">'Signed user'</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">context</span> <span class="s1">'without user logged in'</span> <span class="k">do</span>

  <span class="n">it</span> <span class="s1">'returns false'</span> <span class="k">do</span>
    <span class="n">get</span> <span class="ss">:show</span><span class="p">,</span> <span class="ss">id: </span><span class="mi">2</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">).</span><span class="nf">to</span> <span class="kp">include</span><span class="p">(</span><span class="s1">'Not signed user'</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

end 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
describe ApplicationController do

controller do before_filter :authenticate!, only: [:new]

<span class="k">def</span> <span class="nf">index</span>
  <span class="vi">@current_user</span> <span class="o">=</span> <span class="n">current_user</span>
  <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Hello World'</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">show</span>
  <span class="k">if</span> <span class="n">user_signed_in?</span>
    <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Signed user'</span>
  <span class="k">else</span>
    <span class="n">render</span> <span class="ss">text: </span><span class="s1">'Not signed user'</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">new</span>
  <span class="n">render</span> <span class="ss">text: </span><span class="s1">'A new thing'</span>
<span class="k">end</span>

end

# ...

describe '#authenticate!' do

<span class="n">include_examples</span> <span class="s2">"authentication required"</span> <span class="k">do</span>
  <span class="n">let</span><span class="p">(</span><span class="ss">:action</span><span class="p">)</span> <span class="p">{</span> <span class="n">get</span> <span class="ss">:new</span> <span class="p">}</span>
<span class="k">end</span>

<span class="n">context</span> <span class="s2">"logged in"</span> <span class="k">do</span>

  <span class="n">before</span> <span class="k">do</span>
    <span class="n">sign_in_via_facebook</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
    <span class="n">get</span> <span class="ss">:new</span>
  <span class="k">end</span>

  <span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">respond_with</span><span class="p">(</span><span class="ss">:success</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>

end 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

Sabia que nosso blog agora está no Medium? Confira Aqui!