36

If I have a Devise model User, of which only those users with role :admin are allowed to view a certain url, how can I write an RSpec integration test to check that the status returns 200 for that url?

def login(user)
  post user_session_path, :email => user.email, :password => 'password'
end

This was pseudo-suggested in the answer to this question: Stubbing authentication in request spec, but I can't for the life of me get it to work with devise. CanCan is receiving a nil User when checking Ability, which doesn't have the correct permissions, naturally.

There's no access to the controller in integration specs, so I can't stub current_user, but I'd like to do something like this.

describe "GET /users" do
  it "should be able to get" do
    clear_users_and_add_admin #does what it says...
    login(admin)
    get users_path
    response.status.should be(200)
  end
end

NOTE!!!: all this has changed since the question was asked. The current best way to do this is here: http://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

Community
  • 1
  • 1
pschuegr
  • 1,837
  • 2
  • 18
  • 25
  • 2
    Note: all this has changed since the question was asked. The current best way to do this is here: http://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara Just to reiterate for first sigh readers like me – Jahan Nov 25 '12 at 11:15

6 Answers6

47

@pschuegr's own answer got me across the line. For completeness, this is what I did that gets me easily set up for both request specs and controller specs (using FactoryGirl for creating the user instance):

in /spec/support/sign_in_support.rb:

#module for helping controller specs
module ValidUserHelper
  def signed_in_as_a_valid_user
    @user ||= FactoryGirl.create :user
    sign_in @user # method from devise:TestHelpers
  end
end

# module for helping request specs
module ValidUserRequestHelper

  # for use in request specs
  def sign_in_as_a_valid_user
    @user ||= FactoryGirl.create :user
    post_via_redirect user_session_path, 'user[email]' => @user.email, 'user[password]' => @user.password
  end
end

RSpec.configure do |config|
  config.include ValidUserHelper, :type => :controller
  config.include ValidUserRequestHelper, :type => :request
end

Then in request spec:

describe "GET /things" do
  it "test access to things, works with a signed in user" do
    sign_in_as_a_valid_user
    get things_path
    response.status.should be(200)
  end
end

describe "GET /things" do
  it "test access to things, does not work without a signed in user" do
    get things_path
    response.status.should be(302) # redirect to sign in page
  end
end

and similarly, use 'signed_in_as_valid_user' in controller specs (which wraps Devise::TestHelpers sign_in method with a user from FactoryGirl)

Matt Connolly
  • 9,390
  • 2
  • 60
  • 61
  • I have defined the same modules as you mentioned, but I get this error ` undefined local variable or method `sign_in_as_a_valid_user' for # (NameError)`. Where am I making a mistake? – rohitmishra Oct 08 '12 at 12:54
  • This is in a request spec, right? Is your `Rspec.configure` code executing? – Matt Connolly Oct 09 '12 at 00:20
  • Yes, this is in a request spec. How do I check whether the `Rspec.configure` code executes? – rohitmishra Oct 09 '12 at 07:10
  • I'd put a breakpoint there if you can run your specs through an IDE with a debugger (like Rubymine) or try the `pry` gem, or a good ol' `puts "hello world"` in there :) – Matt Connolly Oct 09 '12 at 07:20
  • @MattConnolly, can you modify your answer to have `require 'spec_helper'` at the top of your request spec? – Purplejacket Dec 18 '13 at 04:23
28

Ah, so close. This does the trick - I was missing the proper parameter form, and the redirecting.

post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
pschuegr
  • 1,837
  • 2
  • 18
  • 25
  • Just what I was looking for. Thanks!f – John Aug 16 '11 at 02:46
  • 4
    I had to use `page.driver.post` instead of `post_via_redirect` (request specs) – Michael De Silva Sep 18 '11 at 18:29
  • 2
    hey how can i user current_user in request spec test cases like how we use in controller spec test cases – sharath Jul 10 '12 at 12:05
  • 2
    I got the error `undefined local variable or method 'user_session_path'` – Phifo Aug 12 '14 at 21:56
  • @Phifo For future references: You can use `Rails.application.routes.url_helpers.user_session_path` or `include Rails.application.routes.url_helpers` beforehand if you use this in place where url_helpers not accessible. But they should be included in tests by default though, as far as I know. – Kirill Fedyanin Aug 31 '16 at 16:09
21

I used a slightly different approach, using the Warden::Test::Helpers.

In my spec/support/macros.rb I added:

module RequestMacros
  include Warden::Test::Helpers

  # for use in request specs
  def sign_in_as_a_user
    @user ||= FactoryGirl.create :confirmed_user
    login_as @user
  end
end

And then included that in RSpec's config in spec_helper.rb:

RSpec.configure do |config|
  config.include RequestMacros, :type => :request
end

And then in the request specs themselves:

describe "index" do
  it "redirects to home page" do
    sign_in_as_a_user 
    visit "/url"
    page.should_not have_content 'content'
  end
end

In contrast to the post_via_redirect user_session_path method, this actually works and allows me to use current_user in before_filters, for example.

Jure Triglav
  • 1,687
  • 15
  • 22
0

As of mid 2017, we have one more, in my opinion better opprotunity to integrate devise in our Rspecs. We are able to utilize stub authentization with helper method sign in defined as described below:

module ControllerHelpers
    def sign_in(user = double('user'))
      if user.nil?
        allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
        allow(controller).to receive(:current_user).and_return(nil)
      else
        allow(request.env['warden']).to receive(:authenticate!).and_return(user)
        allow(controller).to receive(:current_user).and_return(user)
      end
    end
  end

You should also append in spec_helper.rb or rails_helper.rb reference to newly created file:

require 'support/controller_helpers'
  ...
  RSpec.configure do |config|
    ...
    config.include Devise::TestHelpers, :type => :controller
    config.include ControllerHelpers, :type => :controller
    ...
  end

Then in method just place at the beginning of the method body sign_in for context for authenticated user and you are all set. Up to date details can be found in devise docs here

w1t3k
  • 190
  • 1
  • 11
0

You can create a macro (/spec/support/controller_macros.rb) and write something like :

module ControllerMacros
  def login_user
    before(:each) do
      @request.env["devise.mapping"] = :user
      @user = Factory(:user)
      sign_in @user
    end
  end
end

You can also include any CanCan attributes you want. Then, in your spec :

describe YourController do
    login_user

    it "should ..." do

    end
Spyros
  • 41,000
  • 23
  • 80
  • 121
  • 3
    Thanks for your comment Spyros, but it works fine in the controller using the Devise::TestHelpers. It's just in the request specs it doesn't work (@request is nil). – pschuegr May 03 '11 at 06:16
  • You mention that you receive a nil user. This would mean that the spec does not login the user. Are you sure you use devise helpers and login correctly ? You can test that inside your spec to make sure. If not, you can try the code above. I think it all comes from the fact that your spec does not log in a user. – Spyros May 03 '11 at 06:26
  • 2
    This answer is for controller testing, not integration testing, like the question was for - the comments about the nil user submmited by the asker is because the answer is for the wrong situation – Houen Jul 05 '11 at 14:58