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]
Related
I'm using devise for authentication in a small web application, and I'm having a few problems writing some integration tests.
The tests are going to be simple, such as
login works with valid credentials
login rejected with invalid credentials
...
using the techniques that were described in the rails tutorial, but rather than against a home grown authentication system, I'm attempting to retrofit it against devise.
I can use the sign_in function without any problems, and I'm doing that in one or two of my controller tests, e.g.
require 'test_helper'
class mySimpleControllerTest < ActionController::TestCase
include Devise::TestHelpers
def setup
#user = User.create!(
:firstname => "ANOther",
:surname => 'Person',
:username=> 'aperson',
:email => 'aperson#example.com',
:password => 'pass123',
:password_confirmation => 'pass123'
)
sign_in #user
end
test "should get home" do
get :home
assert_response :success
assert_select "title", "TEST PAGE"
end
end
that works wonderfully well. The problem I have is my integration tests for testing the login functionality. I don't want both setup and teardown functions in there, since some tests will have to check against logged out behaviour, some against logged in, and others against reset password etc.
the following test is always is responding with invalid username and password, even though the passwords are correct. Eventually, I want this test to pass when the username or password is incorrect, but right now it responds this way regardless of whether it is or it isn't.
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
test "should be redirected if root_path is called when logged out" do
get root_path
assert_response :redirect
end
test "login with invalid information" do
get new_user_session_path
assert_template 'devise/sessions/new'
post user_session_path, 'user[email]' => 'aperson#example.com', 'user[password]' => 'pass123'
...
I'm assuming the reason for this is because my test database doesn't contain a real user, in fact, the users table is currently empty. Which makes sense, since no user, it should respond with invalid username or password. However, if that is the case, how can I guarantee that the test database is populated with this default user when calling rake test?
After attempting several ways of getting this to work, I eventually moved all tests over to RSpec and Capybara, which will allow me to post data to forms.
I did test whether or not there was an issue with persistent users and this seemed to not be the case. The problem seemed to be that
post user_session_path, 'user[email]' => ...
didn't seem to actually post anything.
I've spent far too long messing with this before asking for help. I can't seem to get RSpec and Sorcery to play together nicely. I've read through the docs on Integration testing with Sorcery and can post the login action properly, but my tests still doesn't think the user is logged in.
# spec/controllers/user_controller_spec
describe 'user access' do
let (:user) { create(:user) }
before :each do
login_user(user[:email], user[:password])
end
it "should log in the user" do
controller.should be_logged_in
end
end
And my login_user method
# spec/support/sorcery_login
module Sorcery
module TestHelpers
module Rails
def login_user email, password
page.driver.post(sessions_path, { email: email , password: password, remember_me: false })
end
end
end
end
The sessions controller handles the pages properly when I use them on the generated pages just fine. I tried outputting the results of the login_user method and it appears to properly post the data. How do I persist this logged in user through the tests? Does a before :each block not work for this? I'm just not sure where it could be running wrong and I'm pretty new to testing/RSpec so I may be missing something obvious. I'd appreciate any help.
Here's the output of the failed tests:
1) UsersController user access should log in the user
Failure/Error: controller.should be_logged_in
expected logged_in? to return true, got false
I just went through this yesterday. Here's what I did, if it helps.
Sorcery provides a test helper login_user that relies on a #controller object being available. This works great in controller specs, but doesn't work in integration tests. So the workaround in integration tests is to write another method (like the one you have above) to simulate actually logging in via an HTTP request (essentially simulating submitting a form).
So my first thought is that you should try renaming your method to login_user_post or something else that doesn't collide with the built-in test helper.
Another potential gotcha is that it looks to me like the Sorcery helper assumes that your user's password is 'secret'.
Here's a link to the built-in helper so you can see what I'm talking about:
https://github.com/NoamB/sorcery/blob/master/lib/sorcery/test_helpers/rails.rb
Good luck - I really like this gem except for this part. It is really only fully explained by patching together SO posts. Here's the code I use:
Integration Helper
module Sorcery
module TestHelpers
module Rails
def login_user_post(user, password)
page.driver.post(sessions_url, { username: user, password: password})
end
def logout_user_get
page.driver.get(logout_url)
end
end
end
end
Integration Spec (where user needs to be logged in to do stuff)
before(:each) do
#user = create(:user)
login_user_post(#user.username, 'secret')
end
Controller Spec (where the regular login_user helper works fine)
before(:each) do
#user = create(:user)
login_user
end
Note that login_user doesn't need any arguments if you have an #user object with the password 'secret'.
Did you try adding to spec/spec_helpers.
RSpec.configure do |config|
# ...
config.include Sorcery::TestHelpers::Rails::Controller
end
Nota that you need to include Sorcery::TestHelpers::Rails::Controller, not just Sorcery::TestHelpers::Rails.
Then you will be able to login_user from any controller specs like:
describe CategoriesController do
before do
#user = FactoryGirl::create(:user)
end
describe "GET 'index'" do
it "returns http success" do
login_user
get 'index'
expect(response).to be_success
end
end
end
The way you pass a password is probably wrong. It may be encrypted at this point. In provided example I will try to do this at first:
describe 'user access' do
let (:user) { create(:user, password: 'secret') }
before :each do
login_user(user[:email], 'secret')
end
it "should log in the user" do
controller.should be_logged_in
end
end
This seems to be very poorly documented. The above solutions did not work for me. Here's how I got it to work:
Check your sessions_url. Make sure it is correct. Also, check what params are necessary to log in. It may be email, username, etc.
module Sorcery
module TestHelpers
module Rails
def login_user_post(email, password)
page.driver.post(sessions_url, { email:email, password: password })
end
end
end
end
RSpec config:
config.include Sorcery::TestHelpers::Rails
Spec helper:
def app
Capybara.app
end
spec/controllers/protected_resource_spec.rb:
describe UsersController do
before do
# Create user
# Login
response = login_user_post( user.email, :admin_password )
expect( response.headers[ 'location' ]).to eq 'http://test.host/'
# I test for login success here. Failure redirects to /sign_in.
#cookie = response.headers[ 'Set-Cookie' ]
end
specify 'Gets protected resource' do
get protected_resource, {}, { cookie:#cookie }
expect( last_response.status ).to eq 200
end
I have been playing with Rails for a couple of years now and have produced a couple of passable apps that are in production. I've always avoided doing any testing though and I have decided to rectify that. I'm trying to write some tests for an app that I wrote for work that is already up and running but undergoing constant revision. I'm concerned that any changes will break things so I want to get some tests up and running. I've read the RSpec book, watched a few screencasts but am struggling to get started (it strikes me as the sort of thing you only understand once you've actually done it).
I'm trying to write what should be a simple test of my ReportsController. The problem with my app is that pretty much the entire thing sits behind an authentication layer. Nothing works if you're not logged in so I have to simulate a login before I can even send forth a simple get request (although I guess I should write some tests to make sure that nothing works without a login - I'll get to that later).
I've set up a testing environment with RSpec, Capybara, FactoryGirl and Guard (wasn't sure which tools to use so used Railscasts' suggestions). The way I've gone about writing my test so far is to create a user in FactoryGirl like so;
FactoryGirl.define do
sequence(:email) {|n| "user#{n}#example.com"}
sequence(:login) {|n| "user#{n}"}
factory :user do
email {FactoryGirl.generate :email}
login {FactoryGirl.generate :login}
password "abc"
admin false
first_name "Bob"
last_name "Bobson"
end
end
and then write my test like so;
require 'spec_helper'
describe ReportsController do
describe "GET 'index'" do
it "should be successful" do
user = Factory(:user)
visit login_path
fill_in "login", :with => user.login
fill_in "password", :with => user.password
click_button "Log in"
get 'index'
response.should be_success
end
end
end
This fails like so;
1) ReportsController GET 'index' should be successful
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/reports_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
Interestingly if I change my test to response.should be_redirect, the test passes which suggests to me that everything is working up until that point but the login is not being recognised.
So my question is what do I have to do to make this login work. Do I need to create a user in the database that matches the FactoryGirl credentials? If so, what is the point of FactoryGirl here (and should I even be using it)? How do I go about creating this fake user in the testing environment? My authentication system is a very simple self-made one (based on Railscasts episode 250). This logging in behaviour will presumably have to replicated for almost all of my tests so how do I go about doing it once in my code and having it apply everywhere?
I realise this is a big question so I thank you for having a look.
The answer depends on your authentication implementation. Normally, when a user logs in, you'll set a session variable to remember that user, something like session[:user_id]. Your controllers will check for a login in a before_filter and redirect if no such session variable exists. I assume you're already doing something like this.
To get this working in your tests, you have to manually insert the user information into the session. Here's part of what we use at work:
# spec/support/spec_test_helper.rb
module SpecTestHelper
def login_admin
login(:admin)
end
def login(user)
user = User.where(:login => user.to_s).first if user.is_a?(Symbol)
request.session[:user] = user.id
end
def current_user
User.find(request.session[:user])
end
end
# spec/spec_helper.rb
RSpec.configure do |config|
config.include SpecTestHelper, :type => :controller
end
Now in any of our controller examples, we can call login(some_user) to simulate logging in as that user.
I should also mention that it looks like you're doing integration testing in this controller test. As a rule, your controller tests should only be simulating requests to individual controller actions, like:
it 'should be successful' do
get :index
response.should be_success
end
This specifically tests a single controller action, which is what you want in a set of controller tests. Then you can use Capybara/Cucumber for end-to-end integration testing of forms, views, and controllers.
Add helper file in spec/support/controller_helpers.rb and copy content below
module ControllerHelpers
def sign_in(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
Now add following lines in spec/rails_helper.rb or spec/spec_helper.rb
file
require 'support/controller_helpers'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.include ControllerHelpers, :type => :controller
end
Now in your controller spec file.
describe "GET #index" do
before :each do
#user=create(:user)
sign_in #user
end
...
end
Devise Official Link
The easiest way to login with a user on feature tests is to use the Warden's helper #login_as
login_as some_user
As I couldn't make #Brandan's answer work, but based on it and on this post, I've came to this solution:
# spec/support/rails_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } # Add this at top of file
...
include ControllerMacros # Add at bottom of file
And
# spec/support/controller_macros.rb
module ControllerMacros
def login_as_admin
admin = FactoryGirl.create(:user_admin)
login_as(admin)
end
def login_as(user)
request.session[:user_id] = user.id
end
end
Then on your tests you can use:
it "works" do
login_as(FactoryGirl.create(:user))
expect(request.session[:user_id]).not_to be_nil
end
For those who don't use Devise:
spec/rails_helper.rb:
require_relative "support/login_helpers"
RSpec.configure do |config|
config.include LoginHelpers
end
spec/support/login_helpers.rb:
module LoginHelpers
def login_as(user)
post "/session", params: { session: { email: user.email, password: "password" } }
end
end
and in the specs:
login_as(user)
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.
Does anyone know how to make rspec follow a redirect (in a controller spec)? (e.g test/unit has follow_redirect!)
I have tried "follow_redirect!" and "follow_redirect" but only get
undefined method `follow_redirect!' for #<Spec::Rails::Example::ControllerExampleGroup::Subclass_1:0xb6df5294>
For example:
When I create an account the page is redirected to accounts page and my new account should be at the top of the list.
it "should create an account" do
post :create, :name => "My New Account"
FOLLOW_REDIRECT!
response.code.should == "200"
accounts = assigns[:accounts]
accounts[0].name.should == "My New Account"
end
But FOLLOW_REDIRECT! needs to be changed to something that actually works.
I think this is the default behavior for rspec-rails controller tests, in the sense that you can set an expectation on the response status and/or path, and test for success.
For example:
it "should create an account" do
post :create
response.code.should == "302"
response.should redirect_to(accounts_path)
end
You can access the redirect location with
response.headers['Location']
you could then request that directly.
If you want to test the redirect you are moving outside of the rspec-rails domain.
You can use Webrat or some other integration-test framework to test this.
The easiest way to solve this without resorting to integration testing is probably to mock out the method that is causing the redirect.
The spec is out of scope, if you want to follow a redirect use request spec, the equivalent of integration test in Test::Unit.
In request specs follow_redirect! works as well as in Test::Unit.
Or if you want to redirect inmediately use _via_redirect as suffix for the verb, example:
post_via_redirect :create, user: #user
Try to use integration/request tests. They are using web-like acces through routing to controllers.
For example:
I have for Rails 2 app in file /spec/integration/fps_spec.rb
require 'spec_helper'
describe "FinPoradci" do
it "POST /fps.html with params" do
fp_params={:accord_id => "FP99998", :under_acc => "OM001", :first_name => "Pavel", :last_name => "Novy"}
fp_test=FinPoradce.new(fp_params)
#after create follow redirection to show
post_via_redirect "/fps", {:fp => fp_params}
response.response_code.should == 200 # => :found , not 302 :created
new_fp=assigns(:fp)
new_fp.should_not be_empty
new_fp.errors.should be_empty
flash[:error].should be_empty
flash[:notice].should_not be_empty
response.should render_template(:show)
end
end
and it works. Until you want to send headers (for basic http authorization).
env={'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user,password)}
post_via_redirect "/fps", {:fp => fp_params}, env
is fine for create, but after redirection it returns 401 and needs new authorization.
SO I have to split it in 2 tests: creation and show on result of creation.
For RSpec / Capybara + Rails
response_headers['Location']
But it works only if there is no delay before redirect.
If it is there, then it's harder to follow the logic.