Where should I put authenticated routes tests using Devise + RSpec? - ruby-on-rails

I'm pretty new to Rails.
We are using Devise to handle user authentication and RSpec for testing the application (Rails 4).
I have an Admin devise model which has access to some authenticated routes. Here's an excerpt from routes.rb:
devise_for :admins
authenticate :admin do
get 'admin', to: 'admin#index'
end
It (obviously) works flawlessly: if I visit /admin, I get redirected to /admins/sign_in and, once I sign in (or if I already am in session) I have direct access to /admin.
Now, as I understand, routes should be tested inside spec/routes/<controller_name>_routes_spec.rb. I like the idea of testing routes (and that the right controller handle each route with the right action etc.) on their own.
We're facing the issue of testing routes when the said routes are authenticated. Including
config.include Devise::TestHelpers[, type: :controller]
inside spec/spec_helper.rb still doesn't make the sign_in (or sign_[out|up]) methods available inside routes specs.
What are we supposed to do? How should we test authenticated routes?
It just feels wrong to me that non authenticated routes are tested as spec/routes, while authenticated routes should be tested inside integration tests, manually filling sign-in forms with Capybara-like stuff.
(note: I read this, but it didn't help at all)

You can include the devise helper in your routes controller by removing the conditional in your spec_helper config. Mine looks like this:
...
RSpec.configure do |config|
config.include Devise::TestHelpers
...
end
def sign_in_user( user=nil )
#user = FactoryGirl.build(:user)
#user.skip_confirmation!
#user.save!
sign_in #user
end
Or if you want to insure that you're not abusing devise in weird place you can include it in just the controller and routing tests.
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.include Devise::TestHelpers, type: :routing
...
end
This is all assuming you're typing your spec files. One of my routing files looks like this:
require "spec_helper"
RSpec.describe WidgetsController, :type => :controller do
describe "routing" do
before(:each) do
sign_in_user
end
it "routes to #index" do
expect(:get => "/widgets").to route_to("widgets#index")
end
it "routes to #new" do
expect(:get => "/widgets/new").to route_to("widgets#new")
end
it "routes to #show" do
expect(:get => "/widgets/1").to route_to("widgets#show", :id => "1")
end
it "routes to #edit" do
expect(:get => "/widgets/1/edit").to route_to("widgets#edit", :id => "1")
end
it "routes to #create" do
expect(:post => "/widgets").to route_to("widgets#create")
end
it "routes to #update" do
expect(:put => "/widgets/1").to route_to("widgets#update", :id => "1")
end
it "routes to #destroy" do
expect(:delete => "/widgets/1").to route_to("widgets#destroy", :id => "1")
end
end
end

Related

Why is my RSpec not loading Devise::Test::ControllerHelpers?

I'm using Rails 5, and Devise 3.5.1.
Going through a nice (older) book about creating/testing an API, which uses Devise authentication. It was written before Rails 5, so I chose not to use the new api-only version.
Here's my test...
#/spec/controllers/api/v1/users_controller_spec.rb
require 'rails_helper'
describe Api::V1::UsersController, :type => :controller do
before(:each) { request.headers['Accept'] = "application/vnd.marketplace.v1" }
describe "GET #show" do
before(:each) do
#user = FactoryGirl.create :user
get :show, params: {id: #user.id}, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql #user.email
end
it { should respond_with 200 }
end
end
And here's a completely unexpected RSpec error
Devise::MissingWarden:
Devise could not find the `Warden::Proxy` instance on your request environment.
Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.
If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object for you.
So I go here - http://www.rubydoc.info/gems/devise/Devise/Test/ControllerHelpers
and tried this -> include Devise::Test::ControllerHelpers
which didn't help because the file controller_helpers.rb is nowhere in my project
What did I miss here?
Thanks
You could add the following to your rails_helper:
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
end
This will include Devise::Test::ControllerHelpers module in all :controller specs.
In the spec_helper.rb add:
config.include Devise::Test::ControllerHelpers, :type => :controller
MiniTest, Rails 4
This works for vanilla Rails 4 MiniTest
test\controllers\users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
include Warden::Test::Helpers
include Devise::Test::ControllerHelpers
setup do
# https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
# #user = users(:admin)
# sign_in #user
end
teardown do
Warden.test_reset!
end
test "login as admin" do
#user = users :admin
sign_in #user
get :dashboard
assert_redirected_to admin_dashboard_path
end
end

Trouble including controller macros for rspec tests /w devise

I am getting a uninitialized constant ControllerMacros (NameError), perhaps similar to these issues (1, 2, 3). I must be screwing up the syntax while trying to include controller macros so I can login with devise and pass controller tests in rspec. Link to GitHub repo.
Rails 4.1.8 and Ruby 2.1.2
spec/controllers/static_pages_controller_spec.rb
require 'rails_helper'
describe StaticPagesController, :type => :controller do
describe "GET #index" do
it "responds successfully with an HTTP 200 status code" do
login_user
get :index
expect(response).to be_success
expect(response).to have_http_status(200)
end
it "renders the index template" do
login_user
get :root
expect(response).to render_template("index")
end
end
end
spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin)
sign_in :user, admin # sign_in(scope, resource)
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.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
added lines to spec/rails_helper
#helps avoid authentication error during rspec:
config.include Devise::TestHelpers, :type => :controller
config.include ControllerMacros, :type => :controller
This worked for me.
spec/support/devise.rb
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Also make sure this line is uncommented in rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
I looks like you may need to add require 'support/controller_macros' to the top of your rails_helper.rb file. This directory would not be included by default with RSpec.

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)

RSpec Rails Login Filter

I recently switched started using rspec-rails(2.6.1) with my Rails(3.0.8) app. I'm used to Test::Unit, and I can't seem to get a filter working for my test methods. I like to keep things as DRY as possible, so I'd like to set up a filter that I can call on any test method that will login as an Authlogic user before the test method is called. I tried accomplishing this by using an RSpec filter in spec_helper.rb:
config.before(:each, :login_as_admin => true) do
post "/user_sessions/create", :user_session => {:username => "admin", :password => "admin"}
end
Then I use it in the corresponding test method(in this case spec/controllers/admin_controller_spec.rb):
require 'spec_helper'
describe AdminController do
describe "GET index" do
it("gives a 200 response when visited as an admin", :login_as_admin => true) do
get :index
response.code.should eq("200")
end
end
end
However, I get this error when I run rspec spec:
Failures:
1) AdminController GET index gives a 200 response when visited as an admin
Failure/Error: Unable to find matching line from backtrace
RuntimeError:
#routes is nil: make sure you set it in your test's setup method.
Blech. Can I only send one HTTP request per test? I also tried stubbing out my authenticate_admin method(inside the config.before block), without any luck.
Unfortunately, there is no way at the moment to do what you're trying to do in a globally defined before hook. The reason is that before hooks are executed in the order in which they get registered, and those declared in RSpec.configure are registered before the one that rspec-rails registers internally to set up the controller, request, response, etc.
Also, this has been reported to https://github.com/rspec/rspec-rails/issues/391.
You should use shulda's macrons. To use shoulda modify your spec_helper.rb
RSpec.configure do |config|
config.include Clearance::Shoulda::Helpers
end
And then can setup filter in controller spec like
require 'spec_helper'
describe AdminController do
fixture :users
before(:each) do
sign_in_as users(:your_user)
end
describe "GET index" do
it("gives a 200 response when visited as an admin", :login_as_admin => true) do
get :index
response.code.should eq("200")
end
end
end

How to use rspec to test named routes?

Given I have a named route:
map.some_route '/some_routes/:id', :controller => 'some', :action => 'other'
How do I use the routing spec file 'spec/routing/some_routing_spec.rb' to test for that named route?
I've tried this after the "describe SomeRouteController" block and it doesn't work, I get 'undefined method "helper":
describe SomeRouteHelper, 'some routes named routes' do
it 'should recognize some_route' do
helper.some_route_path(23).should == '/some_routes/23'
end
end
If this is in a controller spec, you can call the routing method directly, no helper needed.
describe SomeController do
it 'should recognize ma routes!' do
thing_path(23).should == '/things/23'
end
end
In RSpec-Rails 2.7+ you can create a spec/routing directory and put your routing specs in there. See the rspec-rails docs for more info.
there's a nice shoulda matcher for this too:
it { should route(:get, "/users/23").to(:action => "show", :id => 23)
more information on using shoulda matchers with rspec:
https://github.com/thoughtbot/shoulda-matchers
You can do this in your controller specs with the assert_routing method, like so:
describe UsersController do
it "should recognize a specific users#show route" do
assert_routing("/users/23", {:controller => "users", :action => "show", :id => 23})
end
end
More documentation is here.
This is how I write the specs using RSpec2, Shoulda, and Capybara. You would save this example file in #{Rails.root}/spec/routing/thingz_routing_spec.rb or my preference #{Rails.root}/spec/routing/thingz_controller_spec.rb
require "spec_helper"
describe ThingzController do
describe "routing" do
it "routes to #index" do
get("/thingz").should route_to("thingz#index")
end
end
end

Resources