[英]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.