您能同时在RSpec中测试状态变化和返回值吗?

时间:2018-03-13 12:43:34

标签: ruby rspec

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

false

但是这种特殊方法会产生ArgumentError,因为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 需要1个参数。

我可以使用以下可憎的行为:

#and

但这很奇怪。我错过了一些更惯用的东西吗?

3 个答案:

答案 0 :(得分:1)

另一种方法是将返回值粘贴在变量中。

return_value = nil
expect{ return_value = wojit.do_thing }.to change{ wojit.value }
expect( return_value ).to be true

关于YMMV比嵌套expect好还是坏。

答案 1 :(得分: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

然后你的期望就像是

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

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

在这种情况下,actual将是块体(作为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

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

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

只有这些方法中的任何一种问题都是,为了更改而调用一次块,为响应检查调用一次,这取决于您的情况,这可能会导致问题。

答案 2 :(得分:0)

也许不是你想要的,但我实际上认为"更为惯用的东西"将使用describecontext块进行测试,以更好地表达您对相同案例的测试。

describe "When condition is met" do
  it "updates the value" do
    wojit = Wojit.new
    expect { wojit.do_thing }.to change { wojit.value }
  end

  it "returns true" do
    wojit = Wojit.new
    expect(wojit.do_thing).to be_true
  end
end