Search code examples
ruby-on-railsruby-on-rails-4rspecrspec-rails

Why does this delete test not delete my object?


This is what my spec looks like:

  it "should successfullly destroy that user record" do
    expect {
      delete :destroy, :id => @user1.id
    }.to change{User.count}.by(-1)
  end

That calls this action in my UsersController

  def destroy
    @user = User.find(params[:id])
    if @user.destroy
      redirect_to root_path, notice: "You have successfully cancelled your account."
    else
      redirect_to :back
    end
  end

But when I run that spec, I get this:

RSpec::Expectations::ExpectationNotMetError: expected result to have changed by -1, but was changed by 0

How do I fix this?

Edit 1

@user1 is being initialized in a before_each call at the top of the context block that the it statement lives in. That looks like this:

context "when the inviter is being deleted and invited has not accepted invitation" do
  before :each do
    @user1 = create(:user, gender: 0)
    @user2 = create(:user)
    @member = create(:member, email: @user2.email, first_name: @user2.first_name, last_name: @user2.last_name, bio: @user2.bio, gender: @user2.gender)
    @membership = create(:membership, member: @member, family_tree: @user1.family_tree, inviter: @user1, relation: "sister")
    @connection = create(:connection, inviter_membership: @membership)
    login_user
  end

Edit 2

When I swap out the test with this, it works:

  it "should successfullly destroy that user record" do
    delete :destroy, id: @user1
    expect(response).to redirect_to(root_path)
    # expect {
    #   delete :destroy, id: @user1
    # }.to change(User,:count).by(-1)
  end

That test successfully passes.

Finished in 1.2 seconds (files took 6.08 seconds to load)
19 examples, 0 failures, 15 pending

Edit 3

For what it's worth, in my UsersController I am calling load_and_authorize_resource at the top. When I remove that, the test does seem to pass. So the question is, how do I get this to work with CanCanCan?

This is what my ability.rb looks like:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.has_role? :admin
      can :manage, :all
    end

    can :manage, FamilyTree, user_id: user.id
    can :manage, Comment, user_id: user.id
    can :manage, Node, user_id: user.id
    can :manage, Event, user_id: user.id
    can :manage, User, id: user.id

    can :read, Comment, user_id: user.id
    can :read, Event, user_id: user.id
  end
end

Edit 4

When I add a raise to my destroy action...nothing happens. So it does appear that my destroy action is indeed not being executed.

Edit 5

This is what my login_user method looks like:

module SpecAuthentication
  def login_user
    @request.env["devise.mapping"] = Devise.mappings[:user]
    @user = FactoryGirl.create :user
    sign_in @user
  end
end

Solution

  • With the CanCanCan gem you have to allow controller actions. You will need to add this to your ability.rb file.

    can :destroy, User

    This will allow anyone with that role to destroy a user object. You can add more specifics to fit your use case.

    EDIT: Just saw the :manage documentation on the CanCanCan gem.

    Your ability file currently allows users to manage themselves. Your login_user method is creating a user on the fly while your test is trying to destroy a separate user. You can either update your CanCan ability to let all users be destroyed or try destroying current_user.id assuming you have a current_user method. I would strongly suggest changing your login_user method to accept a user object so that you know which user is logged in and have access to that object.

    EDIT:

    As noted above, use a method to sign_in a specific user so that you can access it as an object. Replace the login_user call with sign_in @user1 or a similar method that accepts a user object.