d_rail
d_rail

Reputation: 4119

Rspec test for destroy if no delete link

I'm going through the latest rails tutorial on railstutorial.org and I am stuck on a certain exercise (#8 on http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#sec:updating_deleting_exercises). You have to write a rspec/capybara test to make sure the admin can't delete themself. I have the implementation working, but can't get the test to work right. Here is my code. I found similar questions here: Ruby on Rails syntax and https://getsatisfaction.com/railstutorial/topics/how_to_prevent_admin_user_from_deleting_themselves . But I think it is an older tutorial and not the same question.

Here is the relevant code in spec/requests/user_pages_spec.rb:

describe "User pages" do
  subject { page }
    describe "delete links" do
      describe "as an admin user" do
        let(:admin) { FactoryGirl.create(:admin) }
        before do
          sign_in admin
          visit users_path
        end
        it "should not be able to delete themself" do
          expect { admin.delete }.should_not change(User, :count)
        end
      end
    end
  end
end

The error message says that the user count is getting reduced by 1.

For completeness, here is my (working) implementation:

class UsersController < ApplicationController
  before_filter :current_admin,     only: :destroy
  def current_admin
  @user = User.find(params[:id])
    redirect_to users_path, notice: "Cannot delete current admin" if current_user?(@user)
  end
end

Where am I going wrong, thanks? (I left out some methods, but hopefully there is enough to figure out what I'm trying to do)

Edit: Using Ruby v1.9.3, Rails v3.2.3. By default, there is no delete link for admins.

Edit2: Here is what I got working:

spec/controllers/users_controller_spec.rb

require 'spec_helper'

describe UsersController do
  describe "admins" do
    let(:admin) { FactoryGirl.create(:admin) }

    it "should not be able to delete themself" do
      sign_in admin
      expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
    end
  end
end

users_controller.rb

def destroy
  @user = User.find(params[:id])

  if current_user?(@user)
    flash[:error] = "Cannot delete current admin"
  else
    @user.destroy
    flash[:success] = "User destroyed."
  end
  redirect_to users_path
end

Upvotes: 3

Views: 7293

Answers (2)

BostonPaul
BostonPaul

Reputation: 81

This can be tested in the user_pages_spec which is pretty much what the railstutorial.org book seems to want you to do. (Ruby on Rails Tutorial Chapter 9, Exercise 9). Whether that is a good idea I leave up to greater minds.

The test code in user_pages_spec.rb would look like this:

 describe "I should not be able to delete admins" do
      before { delete user_path(admin.id) }

      it { should_not have_selector('div.alert.alert-error', text: 'Admins cannot delete themselves') }
    end

the delete user_path(admin.id) will mimic clicking 'delete' and works in rspec-capybara. This will pass with your controller code above, assuming you change your error message to match mine or vice-versa.

Also, the before_filter syntax in the UsersController seems to work either with or without the [] if there is only item.

Upvotes: 8

rpedroso
rpedroso

Reputation: 995

The syntax of your before_filter is incorrect. The call should look like this

before_filter :current_admin, :only => [:destroy]

You would also probably be better off keeping this logic in the destroy action. Since it only applies to that action, I don't see any reason to move it into a separate method/filter. The other questions you pointed to are in fact from older versions of the tutorial, but the logic is nonetheless the same:

class UsersController < ApplicationController
  def destroy
    @user = User.find(params[:id])

    if current_user?(@user)
      flash[:error]  = "Cannot delete current admin"
    else
      user.destroy
      flash[:notice] = "User was successfully deleted"
    end

    redirect_to users_path
  end
end

As for your test, it's failing because you are calling the delete method instead of the destroy action in your controller. From ActiveRecord::Relation

Active Record objects are not instantiated, so the object’s callbacks are not executed, including any :dependent association options or Observer methods.

Since they're asking you to use rspec/capybara, you can use the click_link method to trigger the destroy action. Since you're on an index page with multiple listings, you should look into Capybara::Node::Finders in order to select the correct button reliably.

Edit: Since you're looking to test the controller, not the view, you can test with:

describe "admins" do
  let(:admin) { FactoryGirl.create(:admin) }

   it "should not be able to delete themself" do
     sign_in admin
     expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
   end
 end

Upvotes: 7

Related Questions