RSpec: Espere cambiar múltiples

86

Quiero comprobar si hay muchos cambios en un modelo al enviar un formulario en una especificación de función. Por ejemplo, quiero asegurarme de que el nombre de usuario haya cambiado de X a Y, y que la contraseña cifrada haya cambiado por cualquier valor.

Sé que ya hay algunas preguntas sobre eso, pero no encontré una respuesta adecuada para mí. La respuesta más precisa parece la ChangeMultiplede Michael Johnston aquí: ¿Es posible que RSpec espere cambios en dos tablas? . Su desventaja es que solo se verifican cambios explícitos de valores conocidos a valores conocidos.

Creé un pseudo código sobre cómo creo que podría verse un mejor comparador:

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
)

También he creado el esqueleto básico del ChangeMultiplematcher como este:

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

Pero ahora ya recibo este error:

 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'

Cualquier ayuda para crear este comparador personalizado es muy apreciada.

Joshua Muheim
fuente

Respuestas:

183

En RSpec 3 puede configurar varias condiciones a la vez (para que no se rompa la regla de expectativa única). Se vería algo así como:

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 }

Sin embargo, no es una solución completa; en lo que respecta a mi investigación, todavía no hay una manera fácil de hacerlo and_not. Tampoco estoy seguro de su último cheque (si no importa, ¿por qué probarlo?). Naturalmente, debería poder envolverlo dentro de su comparador personalizado .

BroiSatse
fuente
6
si desea esperar que no se cambien varias cosas , simplemente use.and change { @something }.by(0)
stevenspiel
1
¿Puede agregar un segundo ejemplo con todos los paréntesis? Es difícil entender qué métodos están encadenados
Cyril Duchon-Doris
Mi respuesta funciona para Ruby 2 y parece funcionar .should_notpara cualquiera que la necesite
Zack Morris
37

Si desea probar que no se cambiaron varios registros, puede invertir un comparador usando RSpec::Matchers.define_negated_matcher. Entonces, agrega

RSpec::Matchers.define_negated_matcher :not_change, :change

en la parte superior de su archivo (o en su rails_helper.rb) y luego puede encadenar usando and:

expect{described_class.reorder}.to not_change{ruleset.reload.position}.
    and not_change{simple_ruleset.reload.position}
Matthew Hinea
fuente
2

La respuesta aceptada no es 100% correcta, ya que se agregó el soporte completo del comparador compuesto change {}en RSpec versión 3.1.0 . Si intenta ejecutar el código dado en la respuesta aceptada con la versión 3.0 de RSpec, obtendrá un error.

Para utilizar comparadores compuestos con change {}, hay dos formas;

  • La primera es que debe tener al menos la versión 3.1.0 de RSpec .
  • La segunda es que tienes que agregar def supports_block_expectations?; true; enda la RSpec::Matchers::BuiltIn::Compoundclase, ya sea parcheándola o editando directamente la copia local de la gema. Una nota importante: esta forma no es completamente equivalente a la primera, ¡el expect {}bloque se ejecuta varias veces de esta forma!

La solicitud de extracción que agregó el soporte completo de la funcionalidad de comparadores compuestos se puede encontrar aquí .

Foo Bar Zoo
fuente
2

La respuesta de BroiSatse es la mejor, pero si está utilizando RSpec 2 (o tiene comparadores más complejos como .should_not), este método también funciona:

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
fuente
1
¡Ah, linda idea! ¡Probablemente podría construir un contenedor para que sea un poco más fácil de leer!
BroiSatse