您可以同时测试 RSpec 中的状态更改和返回值吗?

Are*_*epo 5 ruby rspec

假设我有一个方法MyKlass#do_thing,我想在测试中只调用一次(因为它可能会更改状态),并且应该true在成功更改状态时返回,false否则返回。我想写一个看起来像这样的规范:

it "Updates myvalue if condition is met" do
  wojit = MyKlass.new

  # ... assuming condition is met
  expect { wojit.do_thing }.to change { wojit.value }.and.be true
end
Run Code Online (Sandbox Code Playgroud)

但是这种特殊的方法会得到一个 ArgumentError,因为#and需要 1 个参数。

我可以让它与以下可憎的东西一起工作:

expect { expect(wojit.do_thing).to be true }.to change { wojit.value }
Run Code Online (Sandbox Code Playgroud)

但这太可怕了。我错过了更惯用的东西吗?

ram*_*ion 5

另一种方法是将返回值保存在变量中。

return_value = nil
expect{ return_value = wojit.do_thing }.to change{ wojit.value }
expect( return_value ).to be true
Run Code Online (Sandbox Code Playgroud)

YMMV 至于它比嵌套expects 更好还是更差。


eng*_*nky 0

Matcher您可以针对这种特定情况实现您自己的自定义,例如:

RSpec::Matchers.define :respond_with do |expected| 
  match do |actual|
    actual.call == expected
  end
  # allow the matcher to support block expectations
  supports_block_expectations
  # make sure this executes in the correct context
  def expects_call_stack_jump?
    true
  end
end
Run Code Online (Sandbox Code Playgroud)

那么你的期望会是这样的

it "Updates myvalue if condition is met" do
  wojit = MyKlass.new
  expect{wojit.do_thing}.to change(wojit, :value).and(respond_with(true))
end
Run Code Online (Sandbox Code Playgroud)

这里的关键是beeq等不支持块期望,因此不能与 结合使用,expect{...}因此我们实现了一个支持块期望 ( supports_block_expectations? #=> true) 的等式匹配器,并将其向上跳转堆栈(在这种情况下这非常重要,否则更改块创建了一个冲突的实际*不确定我 100% 理解为什么,但相信我它确实理解)。

在这种情况下actual,将是块体(作为 a Proc),因此我们只需调用它来将结果与预期值进行比较。

然而,你可以将其进一步抽象为类似的东西

RSpec::Matchers.define :have_response do |expectation| 

  supports_block_expectations

  def expects_call_stack_jump?
    true
  end
  #Actual matching logic 
  match do |actual|
     @actual_value = actual.respond_to?(:call) ? actual.call : actual
    expect(@actual_value).to(expectation)
  end

  failure_message do |actual|
    "expected response to be #{expectation.expected} but response was #{@actual_value}"
  end
  failure_message_when_negated do |actual|
    "expected response not to be #{expectation.expected} but response was #{@actual_value}"
  end

end

#define negation for chaining purposes as needed
RSpec::Matchers.define_negated_matcher :not_have_response, :have_response
Run Code Online (Sandbox Code Playgroud)

这将允许您使用所有不支持块期望的方法,如下所示

it "Updates myvalue if condition is met" do
  wojit = MyKlass.new
  expect{wojit.do_thing}.to change(wojit, :value).and(have_response(be true))
  # or 
  # expect{wojit.do_thing}.to not_have_response(be false).and(change(wojit, :value))
end
Run Code Online (Sandbox Code Playgroud)

这些方法中的任何一种的唯一问题是,该块将被调用一次用于更改,一次用于响应检查,因此根据您的情况,这可能会导致问题。