devise and rspec-rails - How to sign-in user in Request type specs (specs tagged with type: :request)? - devise

Environment
Rails 4.2.0
ruby-2.2.1 [ x86_64 ]
devise 3.4.1
rspec-core 3.2.2
rspec-rails 3.2.1
In my /spec/rails_helper.rb I have included Devise helpers for spec files tagged with type: :controller and type: :request
spec/rails_helper.rb
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:suite) do
begin
DatabaseCleaner.start
FactoryGirl.lint
ensure
DatabaseCleaner.clean
end
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run # ==================> L-60
end
end
config.include FactoryGirl::Syntax::Methods
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
config.include Devise::TestHelpers, type: :controller
config.include Devise::TestHelpers, type: :request
end
With that config in place the type: controller specs runs fine. However when running type: request specs I am getting following error:
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `env' for nil:NilClass
# /home/.rvm/gems/ruby-2.2.1#myapp/gems/devise-3.4.1/lib/devise/test_helpers.rb:24:in `setup_controller_for_warden'
# ./spec/rails_helper.rb:60:in `block (3 levels) in <top (required)>'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/generic/base.rb:15:in `cleaning'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/base.rb:92:in `cleaning'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `call'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `cleaning'
# ./spec/rails_helper.rb:59:in `block (2 levels) in <top (required)>'
https://github.com/plataformatec/devise/blob/master/lib/devise/test_helpers.rb#L24 is following
def setup_controller_for_warden #:nodoc:
#request.env['action_controller.instance'] = #controller # ==================> L-24
end
I am aware that #request instance is not available for :request type specs and hence the error.
Are there any helpers available we can use to sign-in a user in :request type specs when using Devise?
I found a similar issue https://github.com/plataformatec/devise/issues/1114, the reply to which suggests following:
If you're doing integration tests, make sure to sign in your user in the tradicional way, by filling the sign in form and submitting.
But I would like to by pass the actual login for specs which requires a signed-in user.
Thanks.

With the help of a few SO posts(please refer to the References section below) I have managed to achieve the desired solution. I am posting my working code below, in case it can help others looking out for the same:
spec/rails_helper.rb
RSpec.configure do |config|
....
....
config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
end
spec/shared_contexts.rb
RSpec.shared_context "api request global before and after hooks" do
before(:each) do
Warden.test_mode!
end
after(:each) do
Warden.test_reset!
end
end
RSpec.shared_context "api request authentication helper methods" do
def sign_in(user)
login_as(user, scope: :user)
end
def sign_out
logout(:user)
end
end
/spec/requests/api/logout_spec.rb
require 'rails_helper'
require 'shared_contexts'
RSpec.describe "Api Logout", :type => :request do
include_context "api request authentication helper methods"
include_context "api request global before and after hooks"
let(:email) { 'test_user1#example.com' }
let(:password) { 'password' }
# Assumes you have FactoryGirl included in your application's test group.
let!(:user) { create(:user, email: email, password: password) }
context "DELETE /logout" do
it "responds with 204 and signs out the signed-in user" do
sign_in(user)
# Till not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting subject.current_user. If anybody knows a way to do it please share.
# expect(subject.current_user).to_not be_nil
delete "/logout"
expect(response).to have_http_status(204)
end
end
end
I have still not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting expect(subject.current_user).to_not be_nil. If anybody knows a way to do it please share.
References
Integration test with rspec and devise sign_in env
https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
https://github.com/hassox/warden/blob/master/lib/warden/test/helpers.rb
undefined method 'env' for nil:NilClass
https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs
The code in the above link still relies on a request object which is only available in Controller specs. Thus not useful for type: :request specs.
https://github.com/plataformatec/devise/issues/3555
Thanks,
Jiggnesh

While the popular answer here, also replicated on the Devise wiki, is ok, it is simplest to just:
spec/rails_helper.rb
RSpec.configure do |config|
# ...
config.include Devise::Test::IntegrationHelpers, type: :request
end
And just use sign_in in your request spec. This is the equivalent of declaring include Devise::Test::IntegrationHelpers in an system/feature spec or Rails system/controller test.
Reference
Devise wiki: How to sign in and out a user in request type specs - Simple approach

Related

Rails API post in request spec should pass params but getting empty

To start, in my request spec sites.spec.rb I have this test:
describe "POST /v1/sites" do
let(:valid_attributes) { { url: "www.example.com", site_code: "123456" } }
context 'when the request is valid' do
before { post v1_sites_path, params: valid_attributes }
it 'creates a site' do
expect(json['url']).to eq("www.example.com")
expect(json['site_code']).to eq("123456")
end
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
end
I then get a failing test for "creates a site"...
1) Sites POST /v1/sites when the request is valid creates a site
Failure/Error: expect(json['url']).to eq("www.example.com")
expected: "www.example.com"
got: ["can't be blank"]
(compared using ==)
# ./spec/requests/sites_spec.rb:61:in `block (4 levels) in <top (required)>'
# ./spec/rails_helper.rb:84:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:83:in `block (2 levels) in <top (required)>'
Now this technically makes sense because my Site model has validates :url, :site_code, presence: true. So the test is failing because the post is not passing the params correctly.
Lastly, here is the controller:
module Api::V1
class SitesController < BaseApiController
before_action :set_site, only: [:show, :update, :destroy]
# GET /sites
def index
#sites = Site.all
render json: #sites
end
# GET /sites/1
def show
render json: #site
end
# POST /sites
def create
#site = Site.new(site_params)
if #site.save
render json: #site, status: :created, location: #site
else
render json: #site.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /sites/1
def update
if #site.update(site_params)
render json: #site
else
render json: #site.errors, status: :unprocessable_entity
end
end
# DELETE /sites/1
def destroy
#site.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_site
#site = Site.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def site_params
# params.require(:data).require(:attributes).permit(:url, :side_code, :user_id)
# params.require(:site).permit(:url, :side_code, :user_id)
params.fetch(:site, {}).permit(:url, :side_code)
end
end
end
I am speculating that the way I am passing parameters to the post for Rails API is perhaps not formatted or correctly or something else entirely. I did play with the params in the test block trying data: { attributes: valid_attributes } with no luck.
Any thoughts or suggestions are greatly appreciated!
This problem was indeed due to format of the parameters I was passing to POST request in the test block. I tested the POST via the command line and watched the rails server to see how the params were coming through. They looked like this:
Parameters: {"site_code"=>"123456", "url"=>"www.updated.com", "subdomain"=>"api", "id"=>"2", "site"=>{"site_code"=>"123456", "url"=>"www.updated.com"}}
Then in my sites_spec.rb, I copied this format for the valid params of the post request:
let(:valid_attributes) { { "site"=>{"url"=>"www.example.com", "user_id"=>user_id, "site_code"=>"123456"} } }
This works. The JSON format of the params needed to be formatted in the test block the same way they would be if it was real JSON request.

Testing Multiple Custom Validators with RSpec

I am trying to run specs for two custom validators:
spec/validators/email_validator_spec.rb
spec/validators/phone_validator_spec.rb
When I run bundle exec rspec spec/validators/ the phone_validator_spec.rb spec fails:
1) PhoneValidator with a valid phone number should be valid
Failure/Error: subject.should be_valid
expected valid? to return true, got false
# ./spec/validators/phone_validator_spec.rb:20:in `block (4 levels) in <top (required)>'
# ./spec/validators/phone_validator_spec.rb:18:in `each'
# ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'
However, when I run that spec individually using the command bundle exec rspec spec/validators/phone_validator_spec.rb, it passes.
When I remove the email_validator_spec.rb then phone_validator_spec.rb passes using the command bundle exec rspec spec/validators/.
I expect both specs to pass when I run bundle exec rspec spec/validators/. Can anyone explain to me what is happening?
Update:
Used zetetic's tip to print out the error hash:
1) PhoneValidator with a valid phone number should be valid
Failure/Error: subject.errors.should == {}
expected: {}
got: #<ActiveModel::Errors:0x37b2460 #base=#<Validatable:0x37b2700 #validation_context=nil, #errors=#<ActiveModel::Errors:0x37b2460 ...>, #phone_number="1112223333">, #messages={:email=>["is invalid"]}> (using ==)
Diff:
## -1 +1,8 ##
+#<ActiveModel::Errors:0x37b2460
+ #base=
+ #<Validatable:0x37b2700
+ #errors=#<ActiveModel::Errors:0x37b2460 ...>,
+ #phone_number="1112223333",
+ #validation_context=nil>,
+ #messages={:email=>["is invalid"]}>
# ./spec/validators/phone_validator_spec.rb:21:in `block (4 levels) in <top (required)>'
# ./spec/validators/phone_validator_spec.rb:18:in `each'
# ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'
It appears the Validatable class definitions are combined when both specs are run. Is this behavior expected? If I use distinct class names, both specs pass.
spec/validators/phone_validator_spec.rb
require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/phone_validator')
class Validatable
include ActiveModel::Validations
attr_accessor :phone_number
validates :phone_number, phone: true
end
describe PhoneValidator do
subject { Validatable.new }
describe "with a valid phone number" do
it "should be valid" do
phone_numbers = ["1112223333", "123222ABCD"]
phone_numbers.each do |phone_number|
subject.phone_number = phone_number
subject.should be_valid
end
end
end
end
app/validators/phone_validator.rb
class PhoneValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
return if value.blank?
unless value =~ /^[A-Za-z0-9]{10}$/
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
spec/validators/email_validator_spec.rb
require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/email_validator')
class Validatable
include ActiveModel::Validations
attr_accessor :email
validates :email, email: true
end
describe EmailValidator do
subject { Validatable.new }
describe "with a valid email address" do
it "should be valid" do
addresses = %w[user#foo.COM A_US-ER#f.b.org frst.lst#foo.jp a+b#baz.cn]
addresses.each do |valid_address|
subject.email = valid_address
subject.should be_valid
end
end
end
describe "with an invalid phone number" do
it "should be invalid" do
addresses = %w[user#foo,com user_at_foo.org example.user#foo]
addresses.each do |invalid_address|
subject.email = invalid_address
subject.should be_invalid
end
end
end
end
app/validators/email_validator.rb
require 'mail'
class EmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
begin
m = Mail::Address.new(value)
# We must check that value contains a domain and that value is an email address
r = m.domain && m.address == value
t = m.__send__(:tree)
# We need to dig into treetop
# A valid domain must have dot_atom_text elements size > 1
# user#localhost is excluded
# treetop must respond to domain
# We exclude valid email values like <user#localhost.com>
# Hence we use m.__send__(tree).domain
r &&= (t.domain.dot_atom_text.elements.size > 1)
rescue => e
r = false
end
object.errors[attribute] << (options[:message] || "is invalid") unless r
end
end
Using rails 3.2.11, rspec-rails 2.11.0
Your model instance is invalid but you don't know why. Try changing
subject.should be_valid
to
subject.valid?
subject.errors.should == {}
Now the failure message will print out the error hash.
Another tip: Don't rescue Exception.
EDIT
It appears the Validatable class definitions are combined when both specs are run. Is this behavior expected?
Yes, that is normal for Ruby classes. When both spec files are required, each Validatable class body is executed, so you end up with a class that contains both validations.
You need to isolate the validations either by making the subjects pass the validation that is not under test, eg:
subject { Validatable.new(:email => "some value") }
or testing for the specific error message from the validation under test:
subject.valid?
subject.errors(:email).should include("is invalid")
PS. Seriously -- don't rescue Exception. Nothing good will come of that.
I run in to this problem myself, and yes you could rename the class but the solution I used is to create and teardown the Validatable class inside your spec.
Here's a code snippet:
describe "HttpUriValidator",
"Custom validator to ensure URL is a valid URI." do
# Create the dummy class once when the test is run.
before(:all) do
class Validatable
include ActiveModel::Validations
attr_accessor :url
validates :url, http_uri: true
end
end
# Must tearing down the class or it will taint other tests using its
# name.
after(:all) { Object.send(:remove_const, :Validatable) }
subject { Validatable.new }
EDIT::
Just a heads up when you are declaring a Module wrapping your tested class (to avoid namespacing other classes in the test) ie.
module Foo::Bar
describe Something do
after(:all) { Foo::Bar.send(:remove_const, :Testable) }
end
end
you will have to remove the constant from that namespace rather then Object.

Request spec with Devise + Rspec2 + Spork/Guard

I have setup Rspec2 + Spork + Guard + Devise
My files are as follows
#spec_helper.rb
Spork.prefork do
#code
Dir[Rails.root.join('spec/support/**/*.rb')].each {|f| require f}
RSpec.configure do |config|
config.extend ControllerMacros, :type => :controller
end
end
Spork.each_run do
# This code will be run each time you run your specs.
FactoryGirl.reload
include ControllerMacros
end
#spec/support/controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mapping[:user]
user = FactoryGirl.create(:user)
sign_in user
end
end
end
#spec/support/devise.rb
Spec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
in my request spec
#spec/features/documents_spec.rb
require 'spec_helper'
describe "Documents" do
login_user
describe "GET /documents" do
it "should display document name as sameera CV" do
#spec code
end
end
end
and when I run bundle exec guard, I get
1) Documents GET /documents should display document name as sameera CV
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `env' for nil:NilClass
# ./spec/support/controller_macros.rb:4:in `block in login_user'
So far I have done lots of fixes via google and nothing seems to be working, can someone help me :)
I'm on
rails 3.2.9
rspec 2.12.0
devise 2.2.3
any help would be greatly appreciated
Try changing #request.env["devise.mapping"] = Devise.mapping[:user] to request.env["devise.mapping"] = Devise.mapping[:user] in spec/support/controller_macros.rb
Here I'm answering my own question, and I was able to find a workaround for the question I asked.
Following are the steps I did
1) removed the controller_macros.rb and devise.rb from support directory
2) removed the ControllerMacros references from spec_helper.rb
3) Added the following code to
#spec/features/documents_spec.rb
before(:each) do
user = FactoryGirl.create(:user)
visit root_path
fill_in 'user_email', :with => user.email
fill_in 'user_password', :with => user.password
click_button 'Sign in'
end
I'm sure there should be a more elegant way (as describe in devise wiki), but this WORKS :)

Testing a Rails App using Devise with Capybara and Rspec causes no Method Error

I just started some tests on my Rails application in which I use Devise for authentication. I just generated rspec and only added
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
to my spec/support/devise.rb file.
I also added Capybara to the test group in my gemfile, run the bundle command, etc. But as I do visit in my tests, I get a no method error. Here an example of my user_spec.rb:
require 'spec_helper'
describe "Club" do
before(:each) do
#club ||= FactoryGirl.create(:club)
FactoryGirl.create(:membership)
User.all.each{|x| x.delete}
end
describe "privacy" do
it "shouldn't be shown any tournament if the user is private" do
attributes = FactoryGirl.attributes_for(:user)
user = User.create!({private: true}.merge(attributes))
assert(user.private)
visit "/user/#{user.id}/tournaments"
page.should have_no_selector(".tournament-list")
end
end
end
As I run rspec there comes an error like this:
Failures:
1) Club privacy shouldn't be shown any tournament if the user is private
Failure/Error: visit "/user/#{user.id}/tournaments"
NoMethodError:
undefined method `visit' for #<RSpec::Core::ExampleGroup::Nested_3::Nested_1:0x007ff0ea113108>
# ./spec/user_spec.rb:14:in `block (3 levels) in <top (required)>'
For capybara specs I use:
describe "Club", :type => :feature, :js => true do
include Capybara::DSL
it "..."
end
And instead User.all.each{|x| x.delete}, use DatabaseCleaner
You can easily add the Capybara::DSL to RSpec config spec_helper.rb
RSpec.configure do |config|
.....
.......
config.include(Capybara::DSL)
end

FactoryGirl,Rspec2 and devise rails 3

I am using Rspec, FactoryGirl and Spork for my tests.There are 2 things I am a litte unclear on, first is the location of my factories.rb file. At present I have it located in
spec/support/factories.rb
And it looks like this
FactoryGirl.define do
factory :user do
email "example#yahoo.com"
password "password"
password_confirmation "password"
confirmed_at Time.now
end
end
Within my spec_helper I have
config.include FactoryGirl::Syntax::Methods
Secondly I want to login a user before starting my tests for a controller , this particular controller has a before filter :authenticate_user!
I am using devise for my authentication so have added
config.include Devise::TestHelpers, :type => :controller
Reading the devise docs you can add a controller_macros.rb and specify methods like so to use
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
And so i added this also to my spec_helper
config.include ControllerMacros, :type => :controller
So when I add login_user before my controller tests i get undefined method login_user. Am i using two tools here to do the same thing? Do I actually need the devise methods or can it all be done with factoryGirl. If so how do i setup the login process before i can test a controller?
Factories location should be in spec/factories. Check out this example app https://github.com/RailsApps/rails3-devise-rspec-cucumber/tree/master/spec.
For login, generally you seems to doing it right. Check the example app again and here: https://github.com/plataformatec/devise/wiki/How-To:-Controllers-and-Views-tests-with-Rails-3-%28and-rspec%29
For the undefined method login_user error be sure to have
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
and
config.extend ControllerMacros, :type => :controller
in spec_helper. Devise methods should be available wtih subject:
subject.current_user.should_not be_nil