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
我更喜欢这种语法(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
编辑:在避免语法错误之后添加反斜杠
我在尝试使用@MichaelJohnston 的解决方案时遇到语法错误;这是最终对我有用的形式:
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 - 我不知道这个版本是否在解析中有一些细微的变化会导致我出错,看起来这个线程中的其他任何人都没有遇到这个问题。
这应该是两个测试。RSpec 最佳实践要求每个测试一个断言。
describe "#bar" do
subject { lambda { Foo.bar } }
it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end
如果您不想使用前面建议的基于速记/上下文的方法,您也可以执行类似的操作,但请注意它将运行预期两次,因此它可能不适用于所有测试。
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
Georg Ladermann 的语法更好,但它不起作用。测试多个值更改的方法是组合数组中的值。否则,只有最后一个更改断言将决定测试。
这是我的做法:
it "should increment the counters" do
expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end
这与“.to”功能完美结合。
我发现最好的方法是“手动”做:
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)
不是最优雅的解决方案,但它有效
在提出的解决方案都没有被证明确实有效之后,我通过添加一个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
为 change_multiple制作by_at_least
和by_at_most
工作需要一些额外的工作。
我忽略了最佳实践有两个原因:
我这样做的方式(当我需要这样做时)是依靠我的数据库开始为空的事实,所以我可以写:
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
如果您有与我类似的需求,这将起作用,我唯一的建议是睁大眼睛这样做 - 提出的其他解决方案更优雅,但在某些情况下只有几个缺点。