controller is Nil in rspec test - ruby-on-rails

I have following RSpec test:
require 'rails_helper'
require 'spec_helper'
RSpec.describe "Users", type: :request do
describe "sign in/out" do
describe "success" do
it "should sign a user in and out" do
attr = {:name=>"Test1",
:email => "dmishra#test.org",
:password => "foobar",
:password_confirmation => "foobar"
}
user = User.create(attr)
visit signin_path
fill_in "Email", :with => user.email
fill_in "Password", :with => user.password
puts page.body
click_button "Sign in"
controller.should be_signed_in
click_link "Sign out"
controller.should_not be_signed_in
end
end
end
end
I am getting the following error:
Failure/Error: controller.should be_signed_in
expected to respond to `signed_in?
This is because controller is nil. What is wrong here which causes controller to be nil?
Controller class is:
class SessionsController < ApplicationController
def new
#title = "Sign in"
end
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
redirect_to root_path
end
end
signed_in method is defined in session helper which is included.
Ruby platform information:
Ruby: 2.0.0p643
Rails: 4.2.1
RSpec: 3.2.2

This is a request spec (which is basically a rails integration test) which is designed to span multiple requests, possibly across controllers.
The controller variable is set by the request methods that integration testing provides (get, put, post etc.)
If instead you use the capybara DSL (visit, click etc.) then the integration test methods never get called and accordingly controller will be nil. When using capybara you don't have access to individual controller instances so you can't test things such as what signed_in? returns - you have to test a higher level behaviour (e.g. what is on the page).

Related

How to follow redirect to subdomain in capybara RSpec test?

I want to test the authentication mechanism, which upon success redirects to the subdomain, by example:
1. The login form is located in http://example.com
2. Users fill in the form and if credentials are correct, is redirected to http://zone.example.com.
My controller action looks like this:
def create_session
if User.authorize(params[:user])
sign_in user
redirect_to root_url(subdomain: 'zone')
else
redirect_to "#{root_url}#email-login-modal?wrong_password=true"
end
end
The integration test:
context "logging in" do
before(:all) do
#password = 'test123456'
end
let(:user) {create(:user, password: #password)}
scenario "by email", js: true do
visit "/"
page.find("a[href='#email-login-modal']").click
within("#login-form") do
fill_in 'user_email', :with => user.email
fill_in 'user_password', :with => #password
find("button[type='submit']").click
end
expect(page).to have_content user.name
end
end
The login works in development, but the test fails because Capybara does not follow redirection to the subdomain.
This did it for me with Rails 5 and Capybara Chrome driver:
Rails.application.routes.default_url_options[:host] = 'lvh.me'

Rspec: sign_in using capybara doesn't work in controller spec

I'm working on exercise 9 from chapter 9 in Rails Tutorial: http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#fnref-9_9
"Modify the destroy action to prevent admin users from destroying themselves. (Write a test first.)"
I started with creating test:
users_controller_spec.rb
require 'spec_helper'
describe UsersController do
describe "admins-userscontroller" do
let(:admin) { FactoryGirl.create(:admin) }
let(:non_admin) { FactoryGirl.create(:user) }
it "should not be able to delete themself" do
sign_in admin
expect { delete :destroy, :id => admin.id }.not_to change(User, :count)
end
end
end
however, noticed that even if logic for prohibiting admin to delete himself is not implemented, test passes unless I change line
sign_in admin
to
sign_in admin, no_copybara: true
after this change test fails (as expected)
sign_in is in support\utilities.rb file and looks like this:
def sign_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.hash(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
Does anyone know why it doesn't work with capybara? It looks like the "else" section of the above code fails/doesn't execute when using capybara but it doesn't return any error (e.g. that "Email" field is not found, so it looks like it's rendered)...
Other problem is that if I remove non_admin instead of admin:
expect { delete :destroy, :id => non_admin.id }.not_to change(User, :count)
test passes which means non_admin isn't deleted... why does it work for admin and not non_admin?
Question 2:
capybara is not supposed to work in request spec as of 2.0+, but Im using capybara 2.1 and rspec-rails 2.13.1 and it works just fine in request specs (actually that's even what tutorial tells us to do), doesn't even output any warning...

Trouble with refactoring code for rspec feature tests in rails 4

I am trying to give the user of my web app the ability to login with a password. I am rolling my own authentication instead of using a gem. I read this article about refactoring Rspec/Capybara tests:
http://robots.thoughtbot.com/rspec-integration-tests-with-capybara
I liked what I read and decided to give refactoring a try. I created a session helper file for my feature tests.
module Features
module SessionHelpers
def sign_in
user = create(:user)
visit '/authentications/new'
fill_in 'Login', with: user.name
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
end
I then called the sign_in function in my login tests. Here is a little sample.
require 'spec_helper'
feature "signing in" do
before :each do
User.create(:name => 'user#example.com', :password => 'caplin')
end
scenario "user who logs in with correct credentials" do
sign_in
expect(page).to have_content 'Hi user#example.com'
end
end
Unfortunately, I keep getting this error message:
2) signing in user who logs in with correct credentials
Failure/Error: sign_in
NoMethodError:
undefined method `create' for #<RSpec::Core::ExampleGroup::Nested_3:0x007ffc85012438>
# ./spec/support/features/session_helpers.rb:4:in `sign_in'
# ./spec/features/user_logs_in_spec.rb:13:in `block (2 levels) in <top (required)>'
Basically, I need some way to grab the user I created and pass it into the sign_in function. Any hints?
I'm guessing your first issue is a different test configuration than the one the ThoughBot example has. create is not to my knowledge a default method available in RSpec; I'm going to guess they've added every FactoryGirl method to the testing scope. If you're using FactoryGirl, you can get the same behavior by just namespacing the create command:
def sign_in
user = FactoryGirl.create(:user)
visit '/authentications/new'
fill_in 'Login', with: user.name
fill_in 'Password', with: user.password
click_button 'Sign in'
end
However, this won't quite get you everything that you asked for, since you still won't be able to add a custom user. An easy way for this would allow for a user to be passed in:
def sign_in(user=nil)
user ||= FactoryGirl.create(:user)
...
end
This will create the user for you if you don't pass one in on the sign_in call.
Going back to the spec you posted, you'd want to change it to this:
feature "signing in" do
before :each do
#user = User.create(:name => 'user#example.com', :password => 'caplin')
end
scenario "user who logs in with correct credentials" do
sign_in(#user)
expect(page).to have_content 'Hi user#example.com'
end
end
You'd need to attach the user you created to a variable (#user), then pass it to the sign_in as needed.
Problem in you model
module Features
module SessionHelpers
def sign_in
user = create(:user) # <- this method allow only in FactoryGirl
visit '/authentications/new'
fill_in 'Login', with: user.name
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
end
i use another way. Create a class and include FactroyGirl methods and Capybara::DSL like this
class Features
include FactoryGirl::Syntax::Methods
include Capybara::DSL
def sign_in
user = create(:user) #<- FactroyGirl
visit '/authentications/new' #<- Capybara
fill_in 'Login', with: user.name #<- Capybara
fill_in 'Password', with: user.password #<- Capybara
click_button 'Sign in' #<- Capybara
self #<- return page
end
end
in spec
feature "signing in" do
let(:login_user) { Features.new }
scenario "user who logs in with correct credentials" do
page = login_user.sign_in
expect(page).to have_content 'Hi user#example.com'
end
end
You can accomplish this by including FactoryGirl in your tests. Your RSpec configuration block (in spec_helper.rb or in the new version of RSpec rails_helper.rb) should look like this:
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end

RSpec stub not working for integration test

Inside a controllers test, I want to test that when logged in, the controller renders the request fine, else if not logged in, it redirects to the login_path.
The first test passes fine as expected, no user is logged in, so the request is redirected to the login_path. However I've tried a myriad of stub/stub_chain's but still can't get the test to fake a user being logged in and render the page okay.
I would appreciate some direction on getting this to work as expected.
The following classes and tests are the bare bones to keep the question terse.
ApplicationController
class ApplicationController < ActionController::Base
include SessionsHelper
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
SessionsHelper
module SessionsHelper
def logged_in?
redirect_to login_path, :notice => "Please log in before continuing..." unless current_user
end
end
AppsController
class AppsController < ApplicationController
before_filter :logged_in?
def index
#title = "apps"
end
end
apps_controller_spec.rb
require 'spec_helper'
describe AppsController do
before do
#user = FactoryGirl.create(:user)
end
describe "Visit apps_path" do
it "should redirect to login path if not logged in" do
visit apps_path
current_path.should eq(login_path)
end
it "should get okay if logged in" do
#stubs here, I've tried many variations but can't get any to work
#stubbing the controller/ApplicationController/helper
ApplicationController.stub(:current_user).and_return(#user)
visit apps_path
current_path.should eq(apps_path)
end
end
end
This is not working because you are stubbing the method current_user on the ApplicationController class, and not an instance of that class.
I would suggest stubbing it (correctly) on an instance of that class, but your test appears to be an integration test rather than a controller test.
What I would do instead then is as Art Shayderov mentioned is to emulate the sign-in action for a user before attempting to visit a place that requires an authenticated user.
visit sign_in_path
fill_in "Username", :with => "some_guy"
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
In my applications, I've moved this into a helper method for my tests. This is placed into a file at spec/support/authentication_helpers.rb and looks like this:
module AuthenticationHelpers
def sign_in_as!(user)
visit sign_in_path
fill_in "Username", :with => user.username
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
end
end
RSpec.configure do |c|
c.include AuthenticationHelpers, :type => :request
end
Then in my request specs, I simply call the method to sign in as that particular user:
sign_in_as(user)
Now if you want to sign in using a standard controller test, Devise already has helpers for this. I generally include these in the same file (spec/support/authentication_helpers.rb):
RSpec.configure do |c|
c.include Devise::TestHelpers, :type => :controller
end
Then you can sign in using the helpers like this:
before do
sign_in(:user, user)
end
it "performs an action" do
get :index
end
I would look at http://ruby.railstutorial.org/chapters/sign-in-sign-out#sec:a_working_sign_in_method.
The author describes how to write a sign_in method and use it in your rspec tests.
It doesn't look like controller test. It looks more like rspec-rails request spec which simulates browser. So stabbing controller won't work, you have to either simulate sign in (something like this)
visit sign_in
fill_in 'username', :with => 'username'
...
or manually add user_id to session.
If on the other hand you want to test controller in isolation your test should look like that:
get 'index'
response.should be_success

Testing Sessions in Rails 3 with Rspec & Capybara

I'm trying to write integration tests with rspec, factory_girl & capybara. I also have cucumber installed, but I'm not using it (to my knowledge).
I basically want to prepopulate the db with my user, then go to my home page and try to log in. It should redirect to user_path(#user).
However, sessions don't seem to be persisted in my /rspec/requests/ integration tests.
My spec: /rspec/requests/users_spec.rb
require 'spec_helper'
describe "User flow" do
before(:each) do
#user = Factory(:user)
end
it "should login user" do
visit("/index")
fill_in :email, :with => #user.email
fill_in :password, :with => #user.password
click_button "Login"
assert current_path == user_path(#user)
end
end
Returns:
Failures:
1) User flow should login user
Failure/Error: assert current_path == user_path(#user)
<false> is not true.
# (eval):2:in `send'
# (eval):2:in `assert'
# ./spec/requests/users_spec.rb:16
Instead, it redirects to my please_login_path - which should happen if the login fails for any reason (or if session[:user_id] is not set).
If I try to put session.inspect, it fails as a nil object.
If I try to do this in the controller tests (/rspec/controllers/sessions_spec.rb), I can access the session with no problem, and I can call session[:user_id]
If you are using Devise, you'll need to include Warden::Test::Helpers (right after the require of spec_helper is a good place) as outlined in the warden wiki.
The call to session is returning nil because capybara doesn't provide access to it when running as an integration test.
I have the same problems and although filling out a form might be an option for some, I had to roll my own authentication ruby because I was using a third party auth system (Janrain to be exact).... in my tests I ended up using something like this:
Here is what I have in my spec/support/test_helpers_and_stuff.rb
module AuthTestHelper
class SessionBackdoorController < ::ApplicationController
def create
sign_in User.find(params[:user_id])
head :ok
end
end
begin
_routes = Rails.application.routes
_routes.disable_clear_and_finalize = true
_routes.clear!
Rails.application.routes_reloader.paths.each{ |path| load(path) }
_routes.draw do
# here you can add any route you want
match "/test_login_backdoor", to: "session_backdoor#create"
end
ActiveSupport.on_load(:action_controller) { _routes.finalize! }
ensure
_routes.disable_clear_and_finalize = false
end
def request_signin_as(user)
visit "/test_login_backdoor?user_id=#{user.id}"
end
def signin_as(user)
session[:session_user] = user.id
end
end
Then in my request spec, with capybara and selenium, I did the following:
describe "Giveaway Promotion" do
context "Story: A fan participates in a giveaway", js: :selenium do
context "as a signed in user" do
before :each do
#user = Factory(:user)
request_signin_as #user
end
it "should be able to participate as an already signed in user" do
visit giveaway_path
....
end
end
end
end
BTW, I came up with solutions after trying the proposed solutions to this post and this post and neither of them worked for me. (but they certainly inspired my solution)
Good luck!
You've probably moved on from this, but I was just struggling with the same question. Turns out it was a matter of syntax. I was using symbols for :email and :password and I should've been using strings instead ("email" and "password").
In other words, try changing this:
fill_in :email, :with => #user.email
fill_in :password, :with => #user.password
to this:
fill_in "email", :with => #user.email
fill_in "password", :with => #user.password

Resources