RSpec: ожидайте изменения нескольких

86

Я хочу проверить наличие многих изменений в модели при отправке формы в спецификации функции. Например, я хочу убедиться, что имя пользователя было изменено с X на Y, и что зашифрованный пароль был изменен на любое значение.

Я знаю, что по этому поводу уже есть несколько вопросов, но я не нашел для себя подходящего ответа. Наиболее точным ответом кажется ChangeMultipleсопоставление Майкла Джонстона: может ли 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'

Любая помощь в создании этого настраиваемого сопоставителя приветствуется.

Джошуа Мухейм
источник

Ответы:

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пока еще нет. Я также не уверен в вашей последней проверке (если это не имеет значения, зачем ее проверять?). Естественно, вы должны иметь возможность обернуть его в свой собственный сопоставитель .

BroiSatse
источник
6
если вы хотите ожидать, что несколько вещей не изменится, просто используйте.and change { @something }.by(0)
stevenspiel
1
Можете ли вы добавить второй пример со всеми скобками? Мне трудно понять, какие методы связаны,
Сирил Дюшон-Дорис
Мой ответ работает для Ruby 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% правильным, поскольку полная поддержка составного сопоставителя change {}была добавлена ​​в RSpec версии 3.1.0 . Если вы попытаетесь запустить код, указанный в принятом ответе, с RSpec версии 3.0, вы получите сообщение об ошибке.

change {}Есть два способа использовать составные сопоставители с :

  • Во-первых, у вас должна быть как минимум версия RSpec 3.1.0 .
  • Во-вторых, вы должны добавить 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 02