为什么针对destroy_all的单元测试失败?

时间:2014-04-09 09:35:57

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

所以我有这个型号代码:

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 with 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)>'

2 个答案:

答案 0 :(得分:5)

let已加载延迟。因此,在您失败的规范中,这是事件的顺序:

  1. Transaction.cleanup
  2. old_transaction = FactoryGirl.create(:old_transaction)
  3. expect(old_transaction).not_to exist_in_database
  4. 因此,在您尝试清理之后,会创建交易。

    您有多种选择:

    不要将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)。

答案 1 :(得分:1)

我认为你只是偶然在一个&#34;中打字错误:&#34;

试试这个规范:

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