RSpec: Spodziewaj się zmiany wielu

86

Chcę sprawdzić wiele zmian w modelu podczas przesyłania formularza w specyfikacji funkcji. Na przykład chcę się upewnić, że nazwa użytkownika została zmieniona z X na Y i że zaszyfrowane hasło zostało zmienione o dowolną wartość.

Wiem, że jest już kilka pytań na ten temat, ale nie znalazłem dla mnie odpowiedniej odpowiedzi. Najdokładniejsza odpowiedź wydaje się podobna do ChangeMultipledopasowania Michaela Johnstona: Czy jest możliwe, aby RSpec spodziewał się zmiany w dwóch tabelach? . Wadą jest to, że sprawdza się tylko pod kątem wyraźnych zmian ze znanych wartości na znane wartości.

Stworzyłem pseudo kod pokazujący, jak moim zdaniem mógłby wyglądać lepszy dopasowujący:

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
)

Stworzyłem również podstawowy szkielet ChangeMultiple dopasowania w następujący sposób:

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

Ale teraz już otrzymuję ten błąd:

 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'

Każda pomoc w tworzeniu tego niestandardowego dopasowania jest bardzo cenna.

Joshua Muheim
źródło

Odpowiedzi:

183

W RSpec 3 możesz ustawić wiele warunków naraz (aby reguła pojedynczego oczekiwania nie została złamana). Wyglądałoby to tak:

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 }

Nie jest to jednak rozwiązanie kompletne - jeśli chodzi o moje badania, nie ma jeszcze łatwego sposobu and_not. Nie jestem również pewien co do ostatniego sprawdzenia (jeśli to nie ma znaczenia, po co go testować?). Oczywiście powinieneś być w stanie umieścić go w swoim niestandardowym dopasowywaniu .

BroiSatse
źródło
6
jeśli chcesz się spodziewać, że wiele rzeczy się nie zmieni, po prostu użyj.and change { @something }.by(0)
stevenspiel
1
Czy możesz dodać drugi przykład ze wszystkimi nawiasami? Trudno mi zrozumieć, które metody są połączone
Cyril Duchon-Doris
Moja odpowiedź działa dla Ruby 2 i wydaje się działać .should_notdla każdego, kto jej potrzebuje
Zack Morris
37

Jeśli chcesz sprawdzić, czy wiele rekordów nie zostało zmienionych, możesz odwrócić dopasowanie za pomocą RSpec::Matchers.define_negated_matcher. Więc dodaj

RSpec::Matchers.define_negated_matcher :not_change, :change

na początek twojego pliku (lub do twojego rails_helper.rb), a następnie możesz łączyć za pomocą and:

expect{described_class.reorder}.to not_change{ruleset.reload.position}.
    and not_change{simple_ruleset.reload.position}
Matthew Hinea
źródło
2

Zaakceptowana odpowiedź nie jest w 100% poprawna, ponieważ pełna obsługa dopasowywania złożonego dla change {}została dodana w RSpec w wersji 3.1.0 . Jeśli spróbujesz uruchomić kod podany w zaakceptowanej odpowiedzi z RSpec w wersji 3.0, pojawi się błąd.

Aby użyć dopasowań złożonych z change {}, istnieją dwa sposoby;

  • Po pierwsze, musisz mieć co najmniej RSpec w wersji 3.1.0 .
  • Po drugie, musisz dodać def supports_block_expectations?; true; enddo RSpec::Matchers::BuiltIn::Compoundklasy, albo przez małpę łatając ją, albo bezpośrednio edytując lokalną kopię klejnotu. Ważna uwaga: ten sposób nie jest całkowicie równoważny z pierwszym, expect {}blok działa w ten sposób wielokrotnie!

Żądanie ściągnięcia, które dodało pełną obsługę funkcji dopasowań złożonych, można znaleźć tutaj .

Zoo w barze Foo
źródło
2

Odpowiedź BroiSatse jest najlepsza, ale jeśli używasz RSpec 2 (lub masz bardziej złożone dopasowania, takie jak .should_not), ta metoda również działa:

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}
Zack Morris
źródło
1
Ach, fajny pomysł! Prawdopodobnie mógłbyś zbudować opakowanie, aby było trochę łatwiejsze do odczytania!
BroiSatse