RSpec:複数の変更を期待


86

機能仕様でフォームを送信するときに、モデルの多くの変更を確認したいと思います。たとえば、ユーザー名がXからYに変更され、暗号化されたパスワードが任意の値で変更されたことを確認したいと思います。

それについてはすでにいくつか質問があることは知っていますが、私にふさわしい答えは見つかりませんでした。最も正確な答えは、ChangeMultipleここでMichael Johnstonによるマッチャーのようです:RSpecが2つのテーブルの変更を期待することは可能ですか?。その欠点は、既知の値から既知の値への明示的な変更のみをチェックすることです。

より良いマッチャーがどのように見えるかについて、いくつかの擬似コードを作成しました。

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'

このカスタムマッチャーの作成にご協力いただければ幸いです。

回答:


190

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 2017

1
すべて括弧付きの2番目の例を追加できますか?どのメソッドが連鎖しているのか理解するのは難しい
Cyril Duchon-Doris 2017

私の答えは、 Rubyの2のために働くとで動作するように思える.should_notそれを必要とする人のため
ザックモリス

39

複数のレコードが変更されていないことをテストする場合は、を使用してマッチャーを反転できます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

RSpecバージョン3.1.0完全な複合マッチャーサポートchange {}が追加されたため、受け入れられた答えは100%正しくありません。受け入れられた答えで与えられたコードをRSpecバージョン3.0で実行しようとすると、エラーが発生します。

で複合マッチャーを使用するchange {}には、2つの方法があります。

  • 1つ目は、少なくともRSpecバージョン3.1.0が必要です。
  • 2つ目は、モンキーパッチを適用するか、gemのローカルコピーを直接編集def supports_block_expectations?; true; endして、RSpec::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 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.