I am currently trying to test a custom Devise session controller with rspec. My controller looks like this:
class SessionsController < Devise::SessionsController
def create
#valid email?
if !(params[:email] =~ /^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/)
set_flash_message :notice, "Please enter a valid e-mail address!"
end
super
end
end
My RSpec Controller Test is this:
require 'spec_helper'
require 'devise/test_helpers'
describe SessionsController do
it "should put a warning on invalid mail address login attempt" do
post :create, :user => {:email => 'invalidEmailAddress'}
response.should contain "Please enter a valid e-mail address!"
end
it "should put no warning on valid mail address login attempt" do
pending
end
end
If I execute the RSpec Test it fails with the following line:
Failure/Error: post :new, :user => {:email => 'invalidEmailAddress'}
AbstractController::ActionNotFound
# ./spec/controllers/sessions_controller_spec.rb:7
Tips from the plataformatec Devise Wiki as well as this post did not solve this issue. Thanks for your help.
Addition
I investigated further. I was actually able to "remove" the error with the following addition to the controller spec:
before(:each) do
request.env['devise.mapping'] = Devise.mappings[:user]
end
But now a new error appears:
Failure/Error: post :create #currently fails with multiple render warning
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
Even with the create method left out in the inheriting controller the error appears. The error does not appear on get :new for example. It seems to be post :create only.
I am out of ideas? Any help?
Thanks!
I finally fixed my problem by doing including the devise test helpers, calling the method setup_controller_for_warden in my test AND doing request.env["devise.mapping"] = Devise.mappings[:user]. Like so:
require 'test_helper'
class SessionsControllerTest < ActionController::TestCase
include Devise::TestHelpers
test "should reject invalid captcha" do
setup_controller_for_warden
request.env["devise.mapping"] = Devise.mappings[:user]
get :new
assert_response :success
end
end
Not sure about your double render problem though, are you sure your supposed to call post :create then render? i'm not sure how rspec is supposed to work.
Related
I have a controller that depends on the user being authenticated. So it looks like this
class PlansController < ApplicationController
before_action :authenticate_user!
def create
puts "here"
if user_signed_in?
puts "true"
else
puts "false"
end
end
end
My controller tests are working just fine when teh user IS signed in, i.e., when I'm writing something like this:
require 'rails_helper'
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
describe "create action" do
before do
#user = User.create(...)
sign_in :user, #user
end
it "should puts here and then true" do
post :create
# => here
# => true
end
end
But I'd also like to test what happens in the else statement. Not sure how to do this, it fundamentally doesn't even put the here. Is it possible to test this? Or should I just leave and let Devise be?
describe "create action" do
before do
#user = User.create(...)
# do not sign in user (note I have also tried to do a sign_in and then sign_out, same result)
end
it "should puts here and then true" do
post :create
# => nothing is put, not even the first here!
# => no real "error" either, just a test failure
end
end
The before_action :authenticate_user! will immediately redirect you to the default sign-in page, if the user isn't signed in, skipping the create action altogether.
The if user_signed_in? statement is moot in this case, because the user will always be signed in when that code has the chance to run.
If plans can be created with or without an authenticated user, remove the before_action line.
i want to to write a controler test to check that on a successful sign in, a user is redirected to a certain page. the current test I have at the moment is returning a 200.
require 'rails_helper'
RSpec.describe Admin::EntriesController, :type => :controller do
setup_factories
describe "after login" do
it "should redirect to pending after logged in" do
sign_in admin
expect(response).to redirect_to('admin/entries/pending')
end
end
end
which returns
Failure/Error: expect(response).to redirect_to('admin/entries/pending')
Expected response to be a <redirect>, but was <200>
the relevant controller
class AdminController < Devise::RegistrationsController
before_filter :authenticate_admin!
protected
def after_sign_in_path_for(admin)
pending_admin_entries_path
end
end
am I attempting this is in the right way, where am i going wrong?
thanks
sign_in user in RSpec doesn't make a request so you cannot test the redirection.
For after_sign_in_path, you can test like this:
it 'redirects user to pending admin entries path' do
expect(controller.after_sign_in_path(user)).to eq pending_admin_entries_path
end
I'm trying to get RSpec working for a simple scaffolded app, starting with the rspec scaffold tests.
Per the devise wiki, I have added the various devise config entries, a factory for a user and an admin, and the first things I do in my spec controller is login_admin.
Weirdest thing, though... all my specs fail UNLESS I add the following statement right after the it ... do line:
dummy=subject.current_user.inspect
(With the line, as shown below, the specs pass. Without that line, all tests fail with the assigns being nil instead of the expected value. I only happened to discover that when I was putting some puts statements to see if the current_user was being set correctly.)
So what it acts like is that dummy statement somehow 'forces' the current_user to be loaded or refreshed or recognized.
Can anyone explain what's going on, and what I should be doing differently so I don't need the dummy statement?
#specs/controllers/brokers_controller_spec.rb
describe BrokersController do
login_admin
def valid_attributes
{:name => "Bill", :email => "rspec_broker#example.com", :company => "Example Inc", :community_id => 1}
end
def valid_session
{}
end
describe "GET index" do
it "assigns all brokers as #brokers" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :index, {}, valid_session
assigns(:brokers).should eq([broker])
end
end
describe "GET show" do
it "assigns the requested broker as #broker" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :show, {:id => broker.to_param}, valid_session
assigns(:broker).should eq(broker)
end
end
and per the devise wiki here is how I login a :user or :admin
#spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in Factory.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory.create(:user)
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in user
end
end
end
What a struggle! Thank you Robin, I've been googling on this for hours and finally saw your post; now my controller tests are working :)
To add to your answer, I figured out how to get the devise session into the valid_session hash, which allows the controller tests to run properly as generated by rails.
def valid_session
{"warden.user.user.key" => session["warden.user.user.key"]}
end
In your tests, there is the following code:
def valid_session
{}
end
...
get :index, {}, valid_session
Because of this 'session' variable, the "log_in" that you did is essentially not being used during the 'get'.
The way that I solved it was to remove all of the "valid_session" arguments to the get, post, put, delete calls in that controller's spec. The example above becomes:
get :index, {}
I suspect that there's a way to add the devise's session to the "valid_session" hash, but I don't know what it is.
Thanks for this solution.
If you are using a different Devise model, the session id also changes.
For a model Administrator use the following:
def valid_session
{'warden.user.administrator.key' => session['warden.user.administrator.key']}
end
I have a request spec that is trying to test file download functionality in Rails 3.1 for me. The spec (in part) looks like this:
get document_path(Document.first)
logger(response.body)
response.should be_success
It fails with:
Failure/Error: response.should be_success
expected success? to return true, got false
But if I test the download in the browser, it downloads the file correctly.
Here's the action in the controller:
def show
send_file #document.file.path, :filename => #document.file_file_name,
:content_type => #document.file_content_type
end
And my logger gives this information about the response:
<html><body>You are being redirected.</body></html>
How can I get this test to pass?
Update:
As several pointed out, one of my before_filters was doing the redirect. The reason is that I was using Capybara to login in the test, but not using it's methods for navigating around the site. Here's what worked (partially):
click_link 'Libraries'
click_link 'Drawings'
click_link 'GS2 Drawing'
page.response.should be_success #this still fails
But now I can't figure out a way to test the actual response was successful. What am I doing wrong here.
Most likely, redirect_to is being called when you run your test. Here's what I would do to determine the cause.
Add logging to any before filters that could possibly run for this action.
Add logging at several points in the action itself.
This will tell you how far execution gets before the redirect. Which in turn will tell you what block of code (probably a before_filter) is redirecting.
If I had to take a guess off the top of my head, I'd say you have a before_filter that checks if the user is logged in. If that's true, then you'll need to make sure your tests create a logged-in session before you call the login-protected action.
I was getting the same redirect until I realized that my login(user) method was the culprit. Cribbed from this SO link, I changed my login method to:
# file: spec/authentication_helpers.rb
module AuthenticationHelpers
def login(user)
post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
end
In my tests:
# spec/requests/my_model_spec.rb
require 'spec_helper'
require 'authentication_helpers'
describe MyModel do
include AuthenticationHelpers
before(:each) do
#user = User.create!(:email => 'user#email.com', :password => 'password', :password_confirmation => 'password')
login(#user)
end
it 'should run your integration tests' do
# your code here
end
end
[FWIW: I'm using Rails 3.0, Devise, CanCan and Webrat]
i create a customized devise registration controller and i want to test it with rspec.
I've tried it with a very simple test :
it "creates a new parent" do
Parent.should receive(:new)
post :create
end
but i get this exception:
Failures:
1) Parent::RegistrationsController POST create creates a new parent
Failure/Error: post :create, { :commit => "Daftar",
uncaught throw `warden'
# /home/starqle/.rvm/gems/ree-1.8.7-2010.02/gems/devise-1.1.3/lib/devise/hooks/timeoutable.rb:16:in `throw'
# /home/starqle/.rvm/gems/ree-1.8.7-2010.02/gems/devise-1.1.3/lib/devise/hooks/timeoutable.rb:16
I already put this line within my test:
describe Parent::RegistrationsController do
include Devise::TestHelpers
end
I also already put this line:
request.env["devise_mapping"] = Devise.mappings[:parent]
anybody have ideas to solve this problem?
My previous answer is a little confusing. sorry.
Updated answer: root cause is user is not "confirmed" before "sign in".
#user.confirm!
sign_in #user
then everything is fine.
I am fresher in ruby.
I am using rails 3 with devise and factory girl.
I was searching for how to authenticate user for rspec.
I was stucked at before_filter: authenticate_user! in controller.
Finally I got solution (thanks to Siwei Shen)
What I am doing is
include TestHelpers in spec/spec_helper.rb
2.
require 'spec_helper'
describe StudentsController do
before(:each) do
#user = Factory.create(:user) #:user from factory girl with admin privilages
#request.env['devise.mapping'] = :user
#user.confirm!
sign_in #user
end
it "can get index of student" do
get :index
response.should be_suclogin_as #user
end
it "can create student" do
#in student model : validates :name, :presence=> true
post :create, :student => {name => "student1" }
answer = Student.find_by_name("student1")
answer.name.should == "student1"
end
end