【问题标题】:Why Does A Unit Test For destroy_all fail with let?为什么destroy_all的单元测试会因let而失败?
【发布时间】:2014-04-09 21:52:47
【问题描述】:

所以我有这个型号代码:

def self.cleanup
    Transaction.where("created_at < ?", 30.days.ago).destroy_all
  end

还有这个 rspec 单元测试:

describe 'self.cleanup' do
    before(:each) do
      @transaction = Transaction.create(seller:item.user, buyer:user, item:item, created_at:6.weeks.ago)
    end

    it 'destroys all transactions more than 30 days' do
      Transaction.cleanup
      expect(@transaction).not_to exist_in_database
    end
  end

使用这些工厂:

FactoryGirl.define do
  factory :transaction do
    association :seller, factory: :user, username: 'IAMSeller'
    association :buyer, factory: :user, username: 'IAmBuyer'
    association :item
  end

  factory :old_transaction, parent: :transaction do
    created_at 6.weeks.ago
  end
end

使用这个 rspec 自定义匹配器:

RSpec::Matchers.define :exist_in_database do
  match do |actual|
    actual.class.exists?(actual.id)
  end
end

当我将规范更改为:

describe 'self.cleanup' do
    let(:old_transaction){FactoryGirl.create(:old_transaction)}

    it 'destroys all transactions more than 30 days' do
      Transaction.cleanup
      expect(old_transaction).not_to exist_in_database
    end
  end

测试失败。我还尝试手动创建一个事务并将其分配给 :old_transaction 与 let() 但这也会导致测试失败。

为什么只有在 before(:each) 块中使用实例变量时才会通过?

提前致谢!

编辑:失败的输出

1) Transaction self.cleanup destroys all transactions more than 30 days
     Failure/Error: expect(old_transaction).not_to exist_in_database
       expected #<Transaction id: 2, seller_id: 3, buyer_id: 4, item_id: 2, transaction_date: nil, created_at: "2014-02-26 10:06:30", updated_at: "2014-04-09 10:06:32", buyer_confirmed: false, seller_confirmed: false, cancelled: false> not to exist in database
     # ./spec/models/transaction_spec.rb:40:in `block (3 levels) in <top (required)>'

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 rspec


    【解决方案1】:

    let 是延迟加载的。所以在你失败的规范中,这是事件的顺序:

    1. Transaction.cleanup
    2. old_transaction = FactoryGirl.create(:old_transaction)
    3. expect(old_transaction).not_to exist_in_database

    所以事务是在您尝试清理之后创建的。

    您有多种选择:

    不要为此使用let

    除非您有其他规范要告诉其他开发者:

    我完全打算让所有这些规范引用应该是完全相同的对象

    我个人认为,最好内联交易。

    it do
      transaction = FactoryGirl.create(:old_transaction)
    
      Transaction.cleanup
    
      expect(transaction).not_to exist_in_database
    end
    

    使用change 匹配器

    这是我个人的选择,因为它清楚地展示了预期的行为:

    it do
      expect{
        Transaction.cleanup
      }.to change{ Transaction.exists?(old_transaction.id) }.to false
    end
    

    这适用于let,因为change 块在expect 块之前和之后运行。所以在第一次通过时,old_transaction 被实例化,所以它的id 可以被检查。

    在清理之前使用before 或引用old_transaction

    IMO 这似乎很奇怪:

    before do
      old_transaction
    end
    
    it do
      old_transaction # if you don't use the before
      Transaction.clean
      # ...
    end
    

    使用let!

    let! 没有延迟加载。本质上,它是做一个普通的let 的别名,然后在before 中调用它。我不喜欢这种方法(详情请参阅The bang is for surprise)。

    【讨论】:

    • 我选择了第一个选项。虽然我认为如果我的自定义匹配器随时失败,我可能会考虑第二个。
    【解决方案2】:

    我想你只是不小心打错了“:”

    试试这个规范:

    describe 'self.cleanup' do
      let(:old_transaction){FactoryGirl.create(:old_transaction)}
      it 'destroys all transactions more than 30 days' do
        Transaction.cleanup
        expect(old_transaction).not_to exist_in_database
      end
    end
    

    【讨论】:

    • 谢谢,但不幸的是这仍然不起作用。我将继续编辑问题以修复错字,以便其他答案可以以另一种方式看待它
    • 抱歉,@gernberg,这似乎也不是问题所在。我没有对使用 old_transaction 工厂的任何规范使用任何保存。
    • 你从测试中得到什么输出?
    猜你喜欢
    • 1970-01-01
    • 2020-03-23
    • 2013-07-06
    • 2011-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 1970-01-01
    相关资源
    最近更新 更多