RSpec:希望更改多个


86

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

我知道已经有一些问题了,但是我没有找到合适的答案。最准确的答案似乎ChangeMultiple是迈克尔·约翰斯顿(Michael Johnston)的匹配者:RSpec是否有可能期望在两个表中进行更改?。它的缺点是只能检查从已知值到已知值的显式更改。

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

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'

高度赞赏在创建此自定义匹配器方面的任何帮助。

Answers:


183

在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目前尚没有简便的方法。我也不确定您的最后检查(如果没关系,为什么要进行测试?)。当然,您应该能够将其包装在自定义匹配器中


6
如果您希望不要更改多个内容,只需使用.and change { @something }.by(0)
stevenspiel

1
您可以添加带有所有括号的第二个示例吗?我很难理解哪些方法是链接
在一起的

我的回答适用于红宝石2,似乎有工作.should_not的人谁需要它
扎克·莫里斯

37

如果要测试未更改多个记录,可以使用反转匹配器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

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

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

  • 第一个是,您必须至少具有RSpec版本3.1.0
  • 第二个是,你必须添加def supports_block_expectations?; true; endRSpec::Matchers::BuiltIn::Compound类,无论是由猴子修补,或直接编辑宝石的本地副本。重要说明:这种方式并不完全等同于第一种,该expect {}块以这种方式多次运行!

此处可以找到添加了对复合匹配器功能的全面支持的请求请求。


2

BroiSatse的答案是最好的,但是如果您使用的是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}

1
啊,好主意!您可能会构建一个包装器,以使其更容易阅读!
BroiSatse
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.