rails Devise authenticated routes in integration test - ruby-on-rails

I want to test every route in an application, and learned I should do that in an integration test: Where to test routes in ruby on rails
However I'm getting the following error:
NoMethodError: undefined method `authenticate?' for nil:NilClass
/usr/local/Cellar/ruby/1.9.3-p194/lib/ruby/gems/1.9.1/gems/devise-2.1.2/lib/devise/rails/routes.rb:286:in `block in authenticated'
It's well-mentioned online that you can't use Devise::TestHelpers in integration testing --
Devise Google Group,
Devise Github page
How can I test routes like the following?
# config/routes.rb
devise_for :users
authenticated :user do
root to: 'static#home'
end
root to: 'static#landing'
I am running test unit tests with $ rake test:integration

Devise::TestHelpers work by putting things directly into your session. When running integration tests with Capybara, you don't have access to the server-side session. You just have access to the browser.
In our application, our integration tests use helper methods like this, that interact with Devise through the user interface:
def authenticate(user, password = nil)
password ||= FactoryGirl.attributes_for(:user)[:password]
visit new_user_session_path
fill_in 'email', with: user.email
fill_in 'password', with: password
click_on 'Login'
expect(current_path).to eq welcome_path
end

Integration tests are important for your application work flow. They can tell about your URL definition more clearly.
Check the post by nicholaides, that explains the cause of this error and the solution in Authenticated routes.
Still the problem is:
Devise has its own methods and you can't use Devise::TestHelpers in ruby. So how can you test? Well you need to include the Devise::TestHelpers somehow.
Well, if you're using RSpec, you can put the following inside a file named spec/support/devise.rb:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
This is specified here.
But wait.......... Again, you can run into this same issue with Test::Unit.
Then?
So, you just need to add the devise test helpers to test/test_helper.rb.
class ActiveSupport::TestCase
include Devise::TestHelpers
end

Related

Testing Devise with RSpec and Factory Girl

EDIT Read my comment to this question
I'm very new to rails, so please bear with me.
I've been trying to configure a test for Devise using factory girl and rspec. This has taken me the best part of 2 hours, and scouring half the internet to no avail. Even though there is loads of thread on what seems to be my issue, I just cant figure it out.
This is how my /spec files looks like.
GET Home Gives the correct status code
Failure/Error: sign_in user
NoMethodError:
undefined method `sign_in' for #<RSpec::Core::ExampleGroup::Nested_2:0x00000106f32558>
# ./spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)>
This is the error message I get, trying to achieve the following test:
user_spec.rb:
require 'spec_helper'
describe "GET Home" do
before do
##I have tried all sorts of things here. I have also tried to define a module in devise.rb (see note below*), and then call that module here instead of the 2 lines below. But I get the same error, no local variable or undefined method for ...
user = FactoryGirl.create(:user)
sign_in user
end
describe "GET /Home"
it "Gives the correct status code" do
get root_path
response.status.should be(200)
end
end
in spec/factories/users.rb:
FactoryGirl.define do
factory :user do
name "Christoffer"
email "test#test2.com"
password "testtest"
password_confirmation "testtest"
end
end
And the folling lines is included in spec_helpers.rb
config.include FactoryGirl::Syntax::Methods
config.include Devise::TestHelpers, :type => :controller
Now, by doing this, i get the error above. Can anyone possibly explain what I'm doing wrong here? It might be something really obvious, as I'm not really that well rehearsed in the ways of Rails.
*Note (module I tried to define in devise.rb and insert in the before do):
module ValidUserRequestHelper
# Define a method which signs in as a valid user.
def sign_in_as_a_valid_user_nommels
# ASk factory girl to generate a valid user for us.
#user ||= FactoryGirl.create :user
# We action the login request using the parameters before we begin.
# The login requests will match these to the user we just created in the factory, and authenticate us.
post_via_redirect user_session_path, 'user[email]' => #user.email, 'user[password]' => #user.password
end
end
The purpose of 'spec/requests' is for integration tests. You would test features of your app from the user's perspective (ie. fill in certain info, then click button, then so and so should happen if certain inputs are valid or invalid). Spec/models and spec/controllers are usually for unit tests where you test for smaller parts of your app (ie. what happens if the password and password_confirmation params passed to your user model don't match)

Controller Testing with Minitest and Devise failing

In a Rails 3.28 application using Devise, I setup a controller that checks whether a user is logged in or not with if user_signed_in?. Once the tests hit this line, I always get a undefined method 'authenticate!' for nil:NilClass error. I wanted to test some code in a scenario that no user is logged in.
I am thinking that I need to setup the tests according to the options specified in the Devise Wiki here, however, all the instructions pertain to rspec.
In particular, I think I need to have the following to work in minitest:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Has anyone succeded in setting this up to have Devise work with minitest?
Add the following to your minitest_helper.rb file:
class MiniTest::Rails::ActionController::TestCase
include Devise::TestHelpers
end

Devise and OmniAuth twitter integration testing with rspec

I am trying to write a integration test for signing in with twitter using OmniAuth and Devise. I am having trouble getting the request variable to be set. It works in the controller test but not the integration test which leads me to think that I am not configuring the spec helper properly. I have looked around, but I can't seem to find a working solution. Here is what I have so far:
# spec/integrations/session_spec.rb
require 'spec_helper'
describe "signing in" do
before do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
visit new_user_session_path
click_link "Sign in with twitter"
end
it "should sign in the user with the authentication" do
(1+1).should == 3
end
end
This spec raies a error before it can get to the test and I am not quite sure where the request variable needs to be initialized. The error is:
Failure/Error: request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
NoMethodError:
undefined method `env' for nil:NilClass
Now I use the request variable in my controller spec and the test pass but it is not being initialized for the integration tests.
# spec/spec_helper.rb
Dir[Rails.root.join("spec/support/*.rb")].each {|f| require f}
...
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Thanks for the help!
Capybara README says "Access to session and request is not possible from the test", so I gave up to configure in test and decided to write a helper method in application_controller.rb.
before_filter :set_request_env
def set_request_env
if ENV["RAILS_ENV"] == 'test'
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
end
The Devise test helpers are only meant to be used in controller specs not integration specs. In capybara there is no request object so setting it won't work.
What you should do instead is scope loading of Devise test helpers to your controller specs, something like this:
class ActionController::TestCase
include Devise::TestHelpers
end
and use the warden helper for capybara specs as suggested in this guide: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
For a more detailed discussion look at this github issue page: https://github.com/nbudin/devise_cas_authenticatable/issues/36
This one works for me during test using rspec + devise + omniauth + omniauth-google-apps. No doubt the twitter solution will be very similar:
# use this method in request specs to sign in as the given user.
def login(user)
OmniAuth.config.test_mode = true
hash = OmniAuth::AuthHash.new
hash[:info] = {email: user.email, name: user.name}
OmniAuth.config.mock_auth[:google_apps] = hash
visit new_user_session_path
click_link "Sign in with Google Apps"
end
When using request specs with newer versions of RSpec, which do not allow access to the request object:
before do
Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] # If using Devise
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end

How do I simulate a login with RSpec?

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)

Integration test with rspec and devise sign_in env

I am using devise configured to use omniauth facebook sign in integration.
When calling the sign_in method from my spec/request tests I get:
undefined method `env' for nil:NilClass
spec:
describe FacebookController do
include Devise::TestHelpers
it "should display facebook logged in status" do
#user = User.create(:id => "123", :token => "token")
sign_in #user
visit facebook_path
end
end
Your code looks a lot like mine - I was trying to use Capybara and the Devise TestHelper functions, and it turns out you can't, per https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara. The recommended way to do it is explained on that page, and it worked for me.
To be clear, here's what I did - in spec_helper.rb:
RSpec.configure do |config|
config.include Warden::Test::Helpers
end
Warden.test_mode!
And in my code, simply - logout :user.
Here's why, according to the Devise wiki, you cannot use sign_out:
If you're wondering why we can't just use Devise's built in sign_in and sign_out methods, it's because these require direct access to the request object which is not available while using Capybara. To bundle the functionality of both methods together you can create a helper method.
Which, roughly, means that whereas with, say, MiniTest, an object representing the request (#request) is added as an instance variable to the test case class, that doesn't happen with Capybara. I haven't looked at the code to know the details more exactly but basically, Warden expects to find this object to then access the cookie store where the sign in credentials are. With Capybara/RSpec, I expect this isn't happening.

Resources