简体   繁体   English

如何在Rails和RSpec中测试after_destroy回调?

[英]How to test after_destroy callback in Rails and RSpec?

I have this User class in Ruby on Rails: 我在Ruby on Rails中有这个User类:

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). 问题是,即使使用非管理员用户(由Factory Girl生成)进行测试,它似乎也破坏了用户删除操作。 This is my user_controller_spec.rb : 这是我的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. 但是,不应有任何错误,因为默认情况下@user的admin属性设置为false。

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 因此,当您调用delete时,您将始终拥有User.where(“ admin =?”,true).count等于零

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 . 一种替代方法是使被调用的函数ensure_an_admin_remains为公共函数,例如check_admin_remains

You can then test, the logic of check_admin_remains as if it were any other function. 然后,您可以像检查其他函数一样测试check_admin_remains的逻辑。

Then in another test, you can ensure that function is called on destroy without any database interaction as follows: 然后,在另一个测试中,可以确保在无需任何数据库交互的情况下在destroy上调用该函数,如下所示:

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 从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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM