41

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 
4

8 回答 8

78

我更喜欢这种语法(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编辑:在避免语法错误之后添加反斜杠

于 2014-05-10T15:33:27.377 回答
23

我在尝试使用@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 - 我不知道这个版本是否在解析中有一些细微的变化会导致我出错,看起来这个线程中的其他任何人都没有遇到这个问题。

于 2015-07-24T23:31:56.717 回答
20

这应该是两个测试。RSpec 最佳实践要求每个测试一个断言

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

  it { should change { Counter.count }.by 1 }
  it { should change { AnotherCounter.count }.by 1 }
end
于 2012-11-29T00:09:40.680 回答
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
于 2014-01-06T18:45:10.277 回答
4

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

这是我的做法:

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

这与“.to”功能完美结合。

于 2014-05-30T15:27:53.433 回答
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)

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

于 2015-02-22T17:49:29.167 回答
3

在提出的解决方案都没有被证明确实有效之后,我通过添加一个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_leastby_at_most工作需要一些额外的工作。

于 2014-07-06T00:59:26.450 回答
2

我忽略了最佳实践有两个原因:

  1. 我的一组测试是回归测试,我希望它们运行得快,而且它们很少中断。清楚地知道发生了什么问题的好处并不大,重构代码以使其多次运行相同事件的速度对我来说很重要。
  2. 有时候我有点懒,不做那个重构更容易

我这样做的方式(当我需要这样做时)是依靠我的数据库开始为空的事实,所以我可以写:

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

如果您有与我类似的需求,这将起作用,我唯一的建议是睁大眼睛这样做 - 提出的其他解决方案更优雅,但在某些情况下只有几个缺点。

于 2013-11-01T22:04:32.993 回答