Rails response.should be_success is never true - ruby-on-rails

I am following Michael Hartl's excellent tutorial on Ruby on Rails. I'm stuck trying to understand the way ActionDispatch::Response works. This derives from Exercise 9 of Chapter 9 (Rails version 3.2.3).
In particular we're asked to make sure that the admin user is unable to User#destroy himself. I have an idea how to do that, but since I'm trying to follow a TDD methodology, I'm first writing the tests.
This is the relevant snippet in my test:
describe "authorization" do
describe "as non-admin user" do
let(:admin) {FactoryGirl.create(:admin)}
let(:non_admin) {FactoryGirl.create(:user)}
before{valid_signin non_admin}
describe "submitting a DELETE request to the Users#destroy action" do
before do
delete user_path(admin)
#puts response.message
puts response.succes?
end
specify{ response.should redirect_to(root_path) }
specify{ response.should_not be_success }
end
end
#Exercise 9.6-9 prevent admin from destroying himself
describe "as admin user" do
let(:admin){FactoryGirl.create(:admin)}
let(:non_admin){FactoryGirl.create(:user)}
before do
valid_signin admin
end
it "should be able to delete another user" do
expect { delete user_path(non_admin) }.to change(User, :count).by(-1)
end
describe "can destroy others" do
before do
puts admin.admin?
delete user_path(non_admin)
puts response.success?
end
#specify{response.should be_success}
specify{response.should_not be_redirect}
end
describe "cannot destroy himself" do
before do
delete user_path(admin)
puts response.success?
end
#specify{response.should_not be_success}
specify{response.should be_redirect}
end
end
.
.
.
end
All the tests pass except the "can destroy others" test.
However, if I puts response.success? after every delete request, I always get False, so none of the requests "succeed".
Manually interacting with the webapp and deleting users works just fine, so I assume that response.success does not mean that the detroy(or whatever request for that matter) was not successful, but something else. I read it has to do with the difference between HTTP responses 200/302/400, but I'm not totally sure.
For the record, this is my User#destroy:
def destroy
User.find(params[:id]).destroy
flash[:success]="User destroyed."
redirect_to users_path
end
Any light on this?
thanks!
Edit
This is my factory:
FactoryGirl.define do
factory :user do
sequence(:name){ |n| "Person #{n}" }
sequence(:email){ |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
end
Edit 2 as suggested by #Peter Alfvin, I changed lines
let(:user){FactoryGirl.create(:user)}
to
let(:admin){FactoryGirl.create(:admin)}
And all user to admin in general. I also added a puts admin.admin? before the delete request. Still not working!
Edit 3
Changing the test "can destroy others" as:
describe "can destroy others" do
before do
puts admin.admin?
delete user_path(non_admin)
puts response.success?
end
#specify{response.should be_success}
specify{response.should_not be_redirect}
end
Does not seem to help either.

For your "admin" case, you're still creating and logging in as a "regular" user instead of an admin user, which is why you can't destroy anyone else.

response.success does indeed refer to the HTTP response code. By default, I believe this is anything in the 200 range. redirect_to is in the 300 range.

Make sure your user Factory includes this line
factory :user do
#your user factory code
factory :admin do
admin true
end
end
Then FactoryGirl.create(:admin) will return an admin user or you can also use user.toggle!(:admin) which will switch a standard user to an admin user.
try this then
describe "as admin user" do
let(:admin){FactoryGirl.create(:admin)}
let(:non_admin){FactoryGirl.create(:user)}
before do
valid_signin admin
end
it "should be able to delete another user" do
expect { delete user_path(non_admin) }.to change(User, :count).by(-1)
end
it "can destroy others" do #
before do
puts admin.admin?
delete user_path(non_admin)
puts response.success?
end
#specify{response.should be_success}
specify{response.should_not be_redirect}
end
it "cannot destroy himself" do
before do
delete user_path(admin)
puts response.success?
end
#specify{response.should_not be_success}
specify{response.should be_redirect}
end
end
describe creates a magic Class it becomes a subClass of the describe class from my understanding. Rails has a lot of this magic and it can get confusing. Also I have not seen your controller but what are you expecting to happen when you destroy a user because if you followed the tutorial then there will be a redirect delete sent through the browser will call your destroy method in the UsersController which in the tutorial has this line redirect_to users_url so response.should_not be_redirect will always fail because the spec is wrong not the controller.

Related

Undefined local variable or method params in Rspec

Hi I am implementing a method to delete a user account in my web application. My controller:
class UsersController < ApplicationController
before_filter :set_current_user
def user_params
params.require(:user).permit(:user_id, :first_name, :last_name, :email, :password, :password_confirmation)
end
def delete_account
#user = User.find_by_id(params[:id])
if #user.present?
#user.destroy
flash[:notice] = "User Account Deleted."
end
redirect_to root_path
end
def destroy
User.delete(:user_id)
redirect_to root_path
end
end
My rspec:
require 'spec_helper'
require 'rails_helper'
require'factory_girl'
describe UsersController do
describe "delete account" do
before :each do
#fake_results = FactoryGirl.create(:user)
end
it "should call the model method that find the user" do
expect(User).to receive(:find).with(params[:id]).and_return (#fake_results)
end
it "should destroy the user account from the database" do
expect{delete :destroy, id: #fake_results}.to change(User, :count).by(-1)
end
it "should redirect_to the home page" do
expect(response).to render_template(:home)
end
end
end
The first error is
Failure/Error: expect(User).to receive(:find).with(params[:id]).and_return (#fake_results)
NameError:undefined local variable or method `params' for #<RSpec::ExampleGroups::UsersController::DeleteAccount:0x00000007032e18>
I know what this error means but I don't know how to correct it. How can I pass the user id from the controller to rspec?
The second error is:
Failure/Error: expect(response).to render_template(:home)
expecting <"home"> but rendering with <[]>
I think there is something wrong with my controller method. It should redirect to the home page but it doesn't.
params is not available in your tests, it's available in your controller.
Looks like you create a test user in your test:
#fake_results = FactoryGirl.create(:user)
Then, you can use the id of this test user (#fake_results.id) instead of trying to use params[:id]:
expect(User).to receive(:find).with(#fake_results.id).and_return (#fake_results)
Although, you may want to change the name from #fake_results to something more meaningful e.g. test_user or so.
However, this should fix both of your problems as your second problem is there because of the first problem. As it's failing to delete the user in the first place, it's not being redirected to the root path and hence the home template is not rendering.

Rails Controller testing

I am doing the thoughtbot intro to testing program. Im not sure how to test for what they want.
Below is my test.
require "rails_helper"
describe PeopleController do
describe "#create" do
context "when person is valid" do
it "redirects to #show" do
post :create, FactoryGirl.build_stubbed(:person)
expect(response).to redirect_to(show_people_path)
end
end
context "when person is invalid" do
it "redirects to #new" do
pending "create this test"
end
end
end
end
I am of course using factory girl. I have tried several methods. I really don't know hoe to test this controller.
Any insights would be great.
I would create an 'invalid' person using the FactoryGirl, and send it as a parameter to the post :create.
To create an invalid person record, why don't you use nested factories in FactoryGirl? Depending on the validation in your model, you can simply do something like:
spec/factories/person.rb
FactoryGirl.define do
factory :person do
...
factory :invalid_person do
...
email nil
...
end
end
end
in your test
context "when person is invalid" do
it "redirects to #new" do
post :create, FactoryGirl.build_stubbed(:invalid_person)
expect(response).to redirect_to action: :new
end
end

How to test after_destroy callback in Rails and RSpec?

I have this User class in Ruby on Rails:
class User < ActiveRecord::Base
after_destroy :ensure_an_admin_remains
private
def ensure_an_admin_remains
if User.where("admin = ?", true).count.zero?
raise "Can't delete Admin."
end
end
end
This works great and causes a database rollback if someone accidentally deletes an admin user.
The problem is that it seems to break the user delete action, even when testing with a non-admin user (generated by Factory Girl). This is my user_controller_spec.rb:
describe 'DELETE #destroy' do
before :each do
#user = create(:non_admin_user)
sign_in(#user)
end
it "deletes the user" do
expect{
delete :destroy, id: #user
}.to change(User, :count).by(-1)
end
end
Whenever I run this test, I get this error:
Failure/Error: expect{
count should have been changed by -1, but was changed by 0
There shouldn't be any error, though, because #user's admin attribute is set to false by default.
Can anybody help me out here?
Thanks...
I may be wrong but,
Your spec start with empty database right? So there is no admin user present in your db.
So when you call delete, you'll always have User.where("admin = ?", true).count equal to zero
Try creating an user admin before your test
describe 'DELETE #destroy' do
before :each do
create(:admin_user)
#user = create(:non_admin_user)
sign_in(#user)
end
it "deletes the user" do
expect{
delete :destroy, id: #user
}.to change(User, :count).by(-1)
end
end
I would make the following change:
before_destroy :ensure_an_admin_remains
def ensure_an_admin_remains
if self.admin == true and User.where( :admin => true ).count.zero?
raise "Can't delete Admin."
end
end
An alternative is to make the called function ensure_an_admin_remains a public function, such as check_admin_remains.
You can then test, the logic of check_admin_remains as if it were any other function.
Then in another test, you can ensure that function is called on destroy without any database interaction as follows:
let(:user) { build_stubbed(:user) }
it 'is called on destroy' do
expect(user).to receive(:check_admin_remains)
user.run_callbacks(:destroy)
end
You shouldn't raise for control flow. You can halt during callbacks to prevent the record being commited.
I've improved one some of the answers here for anyone else trying to work out how to do this properly as of Rails 5
class User < ActiveRecord::Base
before_destroy :ensure_an_admin_remains
private def ensure_an_admin_remains
return unless admin && User.where(admin: true).limit(2).size == 1
errors.add(:base, "You cannot delete the last admin.")
throw :abort
end
end

How to DRY up RSpec tests shared by different actions in same controller

I have the following tests that I want tested from various actions in the same controller. How can I DRY this up? In the comments below you'll see that the test should call a different method and action depending on which action I'm testing.
shared_examples_for "preparing for edit partial" do
it "creates a new staff vacation" do
StaffVacation.should_receive(:new)
get :new
end
it "assigns #first_day_of_week" do
get :new
assigns(:first_day_of_week).should == 1
end
end
describe "GET new" do
# i want to use 'it_behaves_like "preparing for edit partial"'
# and it should use 'get :new'
end
describe "GET edit" do
# i want to use 'it_behaves_like "preparing for edit partial"'
# but it should use 'get :edit' instead
end
describe "POST create" do
# on unsuccessful save, i want to use 'it_behaves_like "preparing for edit partial"'
# but it should use 'post :create' instead
end
You could do something like this:
shared_examples_for "preparing for edit partial" do
let(:action){ get :new }
it "creates a new staff vacation" do
StaffVacation.should_receive(:new)
action
end
it "assigns #first_day_of_week" do
action
assigns(:first_day_of_week).should == 1
end
end
context 'GET new' do
it_should_behave_like 'preparing for edit partial' do
let(:action){ get :new }
end
end
context 'GET edit' do
it_should_behave_like 'preparing for edit partial' do
let(:action){ get :edit }
end
end
context 'POST create' do
it_should_behave_like 'preparing for edit partial' do
let(:action){ post :create }
end
end
Or, you could use some kind of loop for the examples:
['get :new', 'get :edit', 'post :create'].each do |action|
context action do
it "creates a new staff vacation" do
StaffVacation.should_receive(:new)
eval(action)
end
it "assigns #first_day_of_week" do
eval(action)
assigns(:first_day_of_week).should == 1
end
end
end
One option might be to provide a module mix-in with a method that has your spec inside it.
include Auth # This is your module with your generalized spec inside a method
it "redirects without authentication" do
unauthorized_redirect("get", "new")
end
Then, in our method, we could do a loop through different types of authorization:
module Auth
def unauthorized_redirect(request, action)
[nil, :viewer, :editor].each do |a|
with_user(a) do
eval "#{request} :#{action}"
response.should redirect_to login_path
# whatever other expectations
end
end
end
end

Why doesn't this RSpec stub work?

i have created an rspec test like :
it "should redirect to '/tavern' with an error if user already has a tavern quest" do
user = mock('User')
user.stub(:has_tavern_quest).and_return(true)
post :new_quest, :quest_type => 3
flash[:error].should_not be_nil
response.should redirect_to tavern_path
end
Then, i wrote the controller part :
# check if user already has a tavern quest
if current_user.has_tavern_quest?
flash[:error] = 'You already have a quest to finish !'
redirect_to tavern_path and return
end
And the model part :
def has_tavern_quest?
TavernQuest.exists?(self.id)
end
I would expect that the test succeeds, now but i get :
1) TavernController POST '/quest/' to get a new quest of quest_type == 3 should redirect to '/tavern' with an error if user already has a tavern quest
Failure/Error: flash[:error].should_not be_nil
expected: not nil
got: nil
# ./spec/controllers/tavern_controller_spec.rb:29
Do i have a mistake somewhere ?
THE MACRO FOR LOGIN USER :
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = :user
#user = Factory.create(:user)
sign_in #user
end
end
end
Untested:
it "should redirect to '/tavern' with an error if user already has a tavern quest" do
controller.stub_chain(:current_user,:has_tavern_quest?).and_return(true)
post :new_quest, :quest_type => 3
flash[:error].should_not be_nil
response.should redirect_to tavern_path
end
Your mock doesn't do anything... perhaps you meant to use it somewhere?
I personally dislike mocking in this case and feel it's obfuscation. If you are using Devise you could use their test helpers to sign in as a user.

Resources