From chapter10.23, file: spec/requests/authentication_pages_spec.rb
require 'spec_helper'
describe "Authentication" do
.
.
.
describe "authorization" do
describe "for non-signed-in users" do
let(:user) { FactoryGirl.create(:user) }
.
.
.
describe "in the Microposts controller" do
describe "submitting to the create action" do
before { post microposts_path }
specify { expect(response).to redirect_to(signin_path) }
end
describe "submitting to the destroy action" do
before { delete micropost_path(FactoryGirl.create(:micropost)) }
specify { expect(response).to redirect_to(signin_path) }
end
end
.
.
.
end
end
end
when i tried to pass the test. one of them failed and the message is shown below:
1) Authentication authorization for non-signed-in users in the Microposts controller
submitting to the destroy action
Failure/Error: before { delete micropost_path(FactoryGirl.create(:micropost)) }
ActiveRecord::RecordInvalid:
Validation failed: User can't be blank
# ./spec/requests/authentication_pages_spec.rb:90:in `block (6 levels) in <top (required)>'
the problem is I discontinued from learning ROR for a while so I don't know where I did wrong and why the test failed with this message.
Can anyone help?
I think your FactoryGirl.create(:micropost) is invalid! It looks like you have a vlidation of user on presense in Micropost but you did not specify user in factory.
You should define factory for micropost as
FactoryGirl.define do
factory :micropost do
...
user
...
end
end
or call Factory.create with existed user (you define user before in a let-block): FactoryGirl.create(:micropost, user: user)
You should choose a way according to your needs (workflow logic)
Related
I am writing test for controllers in Rails:
require 'rails_helper'
RSpec.describe GoodsController, type: :controller do
DatabaseCleaner.clean
user = User.create(password: "12345678")
user.save!
describe "GET index" do
it "renders the index template" do
sign_in user
get "index"
expect(response).to render_template("index")
end
end
DatabaseCleaner.clean
end
the GoodsController has this index action I want to test:
def index
if params[:category_id] == nil
#goods = Good.all
else
#goods = Good.where(category_id: params[:category_id])
end
end
and when I run the test, I receive this error:
1) GoodsController GET index renders the index template
Failure/Error: expect(response).to render_template("index")
expecting <"index"> but was a redirect to <http://test.host/users/sign_in>
# ./spec/controllers/goods_controller_spec.rb:12:in `block (3 levels) in <top (required)>'
I've added that sign_in user line, according to other answers in SO, but it didn't help. It still redirects to the logging page. How do I resolve this?
The user you create is not used by rspec when running the Examples (aka tests). It's just a variable inside a block that doesn't do anything useful.
When dealing with fixtures/factories you should either create them in before, let or inside the test itself (it block).
describe "GET index" do
let(:user) { User.create(password: "12345678") }
it "renders the index template" do
# OR, create it here before sign_in
sign_in user
get "index"
expect(response).to render_template("index")
end
end
Not sure if you are using factory_bot, but you should look at it. Usually DatabaseCleaner is set up inside rails_helper, check this SO post for more details.
If you are going to have multiple tests that need the user to be signed in you could also wrap the sign_in in a before hook.
describe "GET index" do
let(:user) { User.create(password: "12345678") }
before do
sign_in user
end
it "renders the index template" do
get "index"
expect(response).to render_template("index")
end
end
I am testing the avaibility of BusinessArea views in the context of signed in / not signed in user.
At the beginning of the test, I create the business area object (test_ba) thanks to the factory, which returns the object.
I 'puts' the test_ba.id to see it created.
Then I request the tested view.
require 'rails_helper'
RSpec.describe BusinessArea, type: :request do
include Warden::Test::Helpers
describe "Business Areas pages: " do
test_ba = FactoryBot.create(:business_area)
puts test_ba.id
context "when not signed in " do
it "should propose to log in when requesting index" do
get business_areas_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show" do
puts test_ba.id
get business_area_path(test_ba)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_areas_path
expect(response).to render_template(:index)
end
it "should display business area" do
puts test_ba.id
get business_area_path(test_ba)
expect(response).to render_template(:show)
end
end
end
end
The test seems to run correctly, but the last step fails due to missing record!?! The ouput returns:
>rspec spec/requests/business_areas_spec.rb
67
.67
..67
F
Failures:
1) BusinessArea Business Areas pages: when signed in should display business area
Failure/Error: #business_area = BusinessArea.find(params[:id])
ActiveRecord::RecordNotFound:
Couldn't find BusinessArea with 'id'=67
# ./app/controllers/business_areas_controller.rb:159:in `set_business_area'
# ./spec/requests/business_areas_spec.rb:35:in `block (4 levels) in <top (required)>'
Finished in 2.07 seconds (files took 13.05 seconds to load)
4 examples, 1 failure
Failed examples:
rspec ./spec/requests/business_areas_spec.rb:33 # BusinessArea Business Areas pages: when signed in should display business area
Can you help me find what's wrong with this?
RSpec has the let and let! methods that create memoized helpers that you should use to setup your test dependency. let is lazy loading (the block is not evaluated until you reference it) while let! is not.
require 'rails_helper'
RSpec.describe BusinessArea, type: :request do
include Warden::Test::Helpers
describe "Business Areas pages: " do
let!(:test_ba){ FactoryBot.create(:business_area) }
context "when not signed in " do
it "should propose to log in when requesting index" do
get business_areas_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show" do
puts test_ba.id
get business_area_path(test_ba)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_areas_path
expect(response).to render_template(:index)
end
it "should display business area" do
puts test_ba.id
get business_area_path(test_ba)
expect(response).to render_template(:show)
end
end
end
end
But wah! Why doesn't my code work?
In RSpec (and in any good test framework) each example runs in isolation and has its own setup and teardown. This includes rolling back the database or clearing it. RSpec does not even run the tests in consecutive order by design.
The record you are defining in the outer context will not be created for each test run. After the first example when the db is is rolled back its gone.
If you want to set something up for each test use before:
require 'rails_helper'
RSpec.describe BusinessArea, type: :request do
include Warden::Test::Helpers
describe "Business Areas pages: " do
before do
#test_ba = FactoryBot.create(:user)
end
context "when not signed in " do
it "should propose to log in when requesting index" do
get business_areas_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show" do
puts #test_ba.id
get business_area_path(test_ba)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
#test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_areas_path
expect(response).to render_template(:index)
end
it "should display business area" do
puts #test_ba.id
get business_area_path(test_ba)
expect(response).to render_template(:show)
end
end
end
end
But let / let! are preferred for setting up simple dependencies.
My application uses Devise for authentication. I want to write integration specs for testing against proper authentication and access prevention.
Somehow, the two don't seem to work together very well. On the devise repo, the README says this on the sign_in and sign_out helpers that Devise gives you for testing:
These helpers are not going to work for integration tests driven by Capybara or Webrat. They are meant to be used with functional tests only. Instead, fill in the form or explicitly set the user in session
So what I'm trying to do to authenticate is filling out the form.
I wrote this (spec/support/signin_helpers.rb):
module SignInHelpers
def sign_in(user)
visit users_login_path
fill_in "Email", with: user.email
fill_in "Passwort", with: "rickroll"
click_button "Einloggen"
end
def login_admin
before(:each) do
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
user = FactoryGirl.create(:user)
sign_in user
end
end
end
And my tests look like this:
describe "unauthorized access" do
login_user
describe "to Companies#new" do
before { get new_company_path }
specify { response.should redirect_to(root_path) }
end
.
.
.
end
Which seems to work, per se, perfectly fine. No "real" errors thrown. But somehow, somewhere, the authentication gets lost:
5) CompaniesManagement unauthorized access to Companies#index should redirect to "/"
Failure/Error: specify { response.should redirect_to(root_path) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/users/login>
# ./spec/requests/companies_management_spec.rb:60:in `block (4 levels) in <top (required)>'
What am I doing wrong?
You have put before(:each) in your spec file instead of in support. I mean
describe "unauthorized access" do
before { login_user }
describe "to Companies#new" do
before { get new_company_path }
specify { response.should redirect_to(root_path) }
end
.
.
.
end
In spec/support/signin_helpers.rb you have to write
def login_user
user = FactoryGirl.create(:user)
sign_in user
end
I'm upgrading a site to use devise and I have the following specs to test SitesController:
describe SitesController do
let(:user) { FactoryGirl.create(:user) }
let(:admin) { FactoryGirl.create(:user, :admin) }
shared_examples "disallow get index" do
get :index
response.should_not be_success
end
context "with user signed in" do
before(:each) { sign_in user }
it "disallowes / with GET" do
get :index
response.should_not be_success
end
it_behaves_like "disallow get index"
end
context "with admin signed in" do
before(:each) { sign_in admin }
it "allowes / with GET" do
get :index
response.should be_success
end
end
end
I want to add a context where no user is signed in and use the shared example disallow get index to specify that you can't do that if you're not signed in. BUT, when I add it_behaves_like "disallow get index" I get this undefined method error:
sites_controller_spec.rb:8:in `block (2 levels) in <top (required)>': undefined method `get' for #<Class:0x00000101746718> (NoMethodError)
So, why does this work when I explicitly call get :index but not in a shared example group?
Turned out to be a very simple fix. I was using shared_examples to replace an it block like this:
shared_examples "disallow get index" do
get :index
response.should_not be_success
end
When shared_examples is really a "replacement" for a context block. So, you need to have it blocks inside your shared_examples:
shared_examples "disallow get index" do
it "fails on index" do
get :index
response.should_not be_success
end
end
I have controller:
def login
if admin_logged_in?
flash[:notice]="You are already logged in"
redirect_to( "/admin/")
else
render(:layout => "admin")
end
end
my RSpec test case
require 'spec_helper'
describe "AdminController" do
before (:each) do
#admin = FactoryGirl.create(:admin)
end
describe "GET 'login'" do
it "should be successful" do
get 'login'
response.should be_success
end
end
end
when I am going execute my test case getting error:
1) AdminController GET 'login' should be successful
Failure/Error: get 'login'
RuntimeError:
#controller is nil: make sure you set it in your test's setup method.
# ./spec/controllers/admin_controller_spec.rb:10:in `block (3 levels) in <top (required)>'
Finished in 0.18799 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/admin_controller_spec.rb:9
In your test, I would replace the describe block of "AdminController" to AdminController. Your test should now look like this...
require 'spec_helper'
describe AdminController do
before (:each) do
#admin = FactoryGirl.create(:admin)
end
describe "GET 'login'" do
it "should be successful" do
get 'login'
response.should be_success
end
end
end
Explanation: Because you wrote AdminController in parenthesis, rspec was not able to tell which controller you were testing.