RSpec:期望改变多个

时间:2015-04-01 10:44:44

标签: ruby-on-rails rspec matcher

我想在功能规范中提交表单时检查模型中的许多更改。例如,我想确保用户名从X更改为Y,并且加密密码已更改为任何值。

我知道已经有一些问题,但我没有找到合适的答案。最准确的答案似乎是Michael Johnston的ChangeMultiple匹配器:Is it possible for RSpec to expect change in two tables?。它的缺点是只检查从已知值到已知值的显式变化。

我创建了一些关于我认为更好的匹配器看起来像的伪代码:

expect {
  click_button 'Save'
}.to change_multiple { @user.reload }.with_expectations(
  name:               {from: 'donald', to: 'gustav'},
  updated_at:         {by: 4},
  great_field:        {by_at_leaset: 23},
  encrypted_password: true,  # Must change
  created_at:         false, # Must not change
  some_other_field:   nil    # Doesn't matter, but want to denote here that this field exists
)

我还创建了ChangeMultiple匹配器的基本骨架:

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

    module BuiltIn
      class ChangeMultiple < Change
        def with_expectations(expectations)
          # What to do here? How do I add the expectations passed as argument?
        end
      end
    end
  end
end

但现在我已经收到了这个错误:

 Failure/Error: expect {
   You must pass an argument rather than a block to use the provided matcher (nil), or the matcher must implement `supports_block_expectations?`.
 # ./spec/features/user/registration/edit_spec.rb:20:in `block (2 levels) in <top (required)>'
 # /Users/josh/.rvm/gems/ruby-2.1.0@base/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:268:in `load'
 # /Users/josh/.rvm/gems/ruby-2.1.0@base/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:268:in `block in load'

非常感谢您在创建此自定义匹配器方面的任何帮助。

4 个答案:

答案 0 :(得分:126)

在RSpec 3中,您可以一次设置多个条件(因此单个期望规则不会被破坏)。它看起来像是:

expect {
  click_button 'Save'
  @user.reload
}.to change { @user.name }.from('donald').to('gustav')
 .and change { @user.updated_at }.by(4)
 .and change { @user.great_field }.by_at_least(23}
 .and change { @user.encrypted_password }

这不是一个完整的解决方案 - 就我的研究而言,还没有简单的方法and_not。我也不确定你的最后一次检查(如果没关系,为什么要测试呢?)。当然,您应该能够将其包裹在custom matcher

答案 1 :(得分:21)

如果要测试多个记录未更改,可以使用RSpec::Matchers.define_negated_matcher反转匹配器。所以,添加

RSpec::Matchers.define_negated_matcher :not_change, :change

到您文件的顶部(或rails_helper.rb),然后您可以使用and链接:

expect{described_class.reorder}.to not_change{ruleset.reload.position}.
    and not_change{simple_ruleset.reload.position}

答案 2 :(得分:1)

接受的答案不是100%正确,因为 RSpec 3.1.0版中添加了对change {}完整复合匹配器支持。如果您尝试使用RSpec 3.0版运行已接受答案中给出的代码,则会出现错误。

为了将复合匹配器与change {}一起使用,有两种方法;

  • 首先,您必须至少 RSpec版本3.1.0
  • 第二个是,您必须将def supports_block_expectations?; true; end添加到RSpec::Matchers::BuiltIn::Compound类中,可以通过猴子修补或直接编辑gem的本地副本。 一个重要提示:这种方式并不完全等同于第一个,expect {}块以这种方式运行多次!

可以找到添加了对复合匹配器功能的完全支持的拉取请求here

答案 3 :(得分:0)

BroiSatse's answer是最好的,但是如果你使用RSpec 2(或者有更复杂的匹配器,如.should_not),这个方法也有效:

lambda {
  lambda {
    lambda {
      lambda {
        click_button 'Save'
        @user.reload
      }.should change {@user.name}.from('donald').to('gustav')
    }.should change {@user.updated_at}.by(4)
  }.should change {@user.great_field}.by_at_least(23}
}.should change {@user.encrypted_password}