RSpec是否有可能期望两个表的变化?

时间:2012-11-29 00:03:49

标签: ruby rspec

RSpec期待改变:

it "should increment the count" do
  expect{Foo.bar}.to change{Counter.count}.by 1
end

有没有办法预期两个表的变化?

expect{Foo.bar}.to change{Counter.count}.by 1 
and change{AnotherCounter.count}.by 1 

8 个答案:

答案 0 :(得分:75)

我更喜欢这种语法(rspec 3或更高版本):

it "should increment the counters" do
  expect { Foo.bar }.to change { Counter,        :count }.by(1).and \
                        change { AnotherCounter, :count }.by(1)
end

是的,这是一个地方的两个断言,但由于块只执行了一次,它可以加速测试。

编辑:在.and之后添加反斜杠以避免语法错误

答案 1 :(得分:22)

我在尝试使用@MichaelJohnston's solution时出现语法错误;这是最终为我工作的形式:

it "should increment the counters" do
  expect { Foo.bar }.to change { Counter.count }.by(1)
    .and change { AnotherCounter.count }.by(1)
end

我应该提到我使用ruby 2.2.2p95 - 我不知道这个版本在解析中是否有一些微妙的变化导致我出错,它并不会出现在其他人身上在这个帖子中遇到了这个问题。

答案 2 :(得分:20)

这应该是两个测试。 RSpec best practices call for one assertion per test

describe "#bar" do
  subject { lambda { Foo.bar } }

  it { should change { Counter.count }.by 1 }
  it { should change { AnotherCounter.count }.by 1 }
end

答案 3 :(得分:11)

如果您不想使用之前建议的基于速记/上下文的方法,您也可以执行类似的操作,但要注意它会运行两次预期,因此可能不适合所有测试。

it "should increment the count" do
  expectation = expect { Foo.bar }
  expectation.to change { Counter.count }.by 1
  expectation.to change { AnotherCounter.count }.by 1
end

答案 4 :(得分:4)

我发现的最好方法是“手动”:

counters_before         = Counter.count
another_counters_before = AnotherCounter.count
Foo.bar
expect(Counter.count).to eq (counters_before + 1)
expect(AnotherCounter.count).to eq (another_counters_before + 1)

不是最优雅的解决方案,但它有效

答案 5 :(得分:3)

Georg Ladermann的语法更好,但它不起作用。测试多个值更改的方法是组合数组中的值。否则,只有最后一次更改断言将决定测试。

我是这样做的:

it "should increment the counters" do
  expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end

这完全适用于' .to'功能

答案 6 :(得分:2)

我忽略了最佳做法有两个原因:

  1. 我的一组测试是回归测试,我希望它们能够快速运行,并且 他们很少休息。准确清晰的优点 什么是破坏并不是巨大的,以及重构我的代码的速度减慢 所以它多次运行相同的事件对我来说很重要。
  2. 我有时候有点懒,而且不做那个重构更容易
  3. 我这样做的方式(当我需要这样做时)是依赖于我的数据库开始为空的事实,所以我可以写:

    foo.bar
    expect(Counter.count).to eq(1)
    expect(Anothercounter.count).to eq(1)
    

    在某些情况下,我的数据库不是空的,但我要么知道之前的计数,要么我可以明确地测试之前的计数:

    counter_before = Counter.count
    another_counter_before = Anothercounter.count
    
    foo.bar
    
    expect(Counter.count - counter_before).to eq(1)
    expect(Anothercounter.count - another_counter_before).to eq(1)
    

    最后,如果您有很多要检查的对象(我有时会这样做),您可以这样做:

    before_counts = {}
    [Counter, Anothercounter].each do |classname|
      before_counts[classname.name] = classname.count
    end
    
    foo.bar
    
    [Counter, Anothercounter].each do |classname|
      expect(classname.count - before_counts[classname.name]).to be > 0
    end
    

    如果你对我有类似的需求,那么我的唯一建议就是睁大眼睛这样做 - 其他提出的解决方案更优雅,但在某些情况下会有一些缺点。

答案 7 :(得分:2)

在提出的解决方案都没有得到证实后,我通过添加change_multiple匹配器来实现这一点。这仅适用于RSpec 3,而不适用于2. *

module RSpec
  module Matchers
    def change_multiple(receiver=nil, message=nil, &block)
      BuiltIn::ChangeMultiple.new(receiver, message, &block)
    end
    alias_matcher :a_block_changing_multiple,  :change_multiple
    alias_matcher :changing_multiple,          :change_multiple

    module BuiltIn
      class ChangeMultiple < Change
        private

          def initialize(receiver=nil, message=nil, &block)
            @change_details = ChangeMultipleDetails.new(receiver, message, &block)
          end
      end
      class ChangeMultipleDetails < ChangeDetails
        def actual_delta
          @actual_after = [@actual_after].flatten
          @actual_before = [@actual_before].flatten
          @actual_after.map.with_index{|v, i| v - @actual_before[i]}
        end
      end
    end
  end
end

用法示例:

it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do
  a = "." * 4
  b = "." * 10
  times_called = 0
  expect {
    times_called += 1
    a += ".."
    b += "-----"
  }.to change_multiple{[a.length, b.length]}.by([2,5])
  expect(times_called).to eq(1)
end

by_at_leastby_at_most为change_multiple工作需要一些额外的工作。