¿Es posible que RSpec espere cambios en dos tablas?

RSpec espera cambio:

it "should increment the count" do expect{Foo.bar}.to change{Counter.count}.by 1 end 

¿Hay alguna manera de esperar cambios en dos tablas?

 expect{Foo.bar}.to change{Counter.count}.by 1 and change{AnotherCounter.count}.by 1 

Esto debería ser dos pruebas. Las mejores prácticas de RSpec requieren una aserción por prueba .

 describe "#bar" do subject { lambda { Foo.bar } } it { should change { Counter.count }.by 1 } it { should change { AnotherCounter.count }.by 1 } end 

Prefiero esta syntax (rspec 3 o posterior):

 it "should increment the counters" do expect { Foo.bar }.to change { Counter, :count }.by(1).and \ change { AnotherCounter, :count }.by(1) end 

Sí, estas son dos afirmaciones en un solo lugar, pero como el bloque se ejecuta solo una vez, puede acelerar las pruebas.

EDITAR: Se agregó Backslash después del error .and para evitar el error de syntax

Recibí errores de syntax al utilizar la solución de @ MichaelJohnston ; Esta es la forma que finalmente me funcionó:

 it "should increment the counters" do expect { Foo.bar }.to change { Counter.count }.by(1) .and change { AnotherCounter.count }.by(1) end 

Debo mencionar que estoy usando ruby ​​2.2.2p95. No sé si esta versión tiene algún cambio sutil en el análisis que me cause errores, no parece que nadie más en este hilo haya tenido ese problema.

Si no desea utilizar el método basado en la taquigrafía / contexto que se sugirió anteriormente, también puede hacer algo como esto, pero se le advierte que ejecutará la expectativa dos veces, por lo que podría no ser adecuado para todas las pruebas.

 it "should increment the count" do expectation = expect { Foo.bar } expectation.to change { Counter.count }.by 1 expectation.to change { AnotherCounter.count }.by 1 end 

La mejor manera que he encontrado es hacerlo “manualmente”:

 counters_before = Counter.count another_counters_before = AnotherCounter.count Foo.bar expect(Counter.count).to eq (counters_before + 1) expect(AnotherCounter.count).to eq (another_counters_before + 1) 

No es la solución más elegante pero funciona.

La syntax de Georg Ladermann es más agradable pero no funciona. La forma de probar múltiples cambios de valor es mediante la combinación de los valores en las matrices. Si no, solo la última afirmación de cambio decidirá sobre la prueba.

Así es como lo hago:

 it "should increment the counters" do expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1]) end 

Esto funciona perfectamente con la función ‘.to’.

Estoy ignorando las mejores prácticas por dos razones:

  1. Un conjunto de mis pruebas son pruebas de regresión, quiero que se ejecuten rápido y se rompen rara vez. La ventaja de tener claridad sobre qué es exactamente lo que se está rompiendo no es enorme, y la desaceleración de la refactorización de mi código para que se ejecute el mismo evento varias veces es material para mí.
  2. A veces soy un poco perezoso, y es más fácil no hacer ese refactor

La forma en que hago esto (cuando necesito hacerlo) es confiar en el hecho de que mi base de datos comienza vacía, por lo que podría escribir:

 foo.bar expect(Counter.count).to eq(1) expect(Anothercounter.count).to eq(1) 

En algunos casos, mi base de datos no está vacía, pero o bien conozco el recuento anterior o puedo probar explícitamente el recuento anterior:

 counter_before = Counter.count another_counter_before = Anothercounter.count foo.bar expect(Counter.count - counter_before).to eq(1) expect(Anothercounter.count - another_counter_before).to eq(1) 

Finalmente, si tienes muchos objetos para verificar (a veces lo hago) puedes hacer esto como:

 before_counts = {} [Counter, Anothercounter].each do |classname| before_counts[classname.name] = classname.count end foo.bar [Counter, Anothercounter].each do |classname| expect(classname.count - before_counts[classname.name]).to be > 0 end 

Si tiene necesidades similares, esto funcionará, mi único consejo sería hacerlo con los ojos abiertos. Las otras soluciones propuestas son más elegantes, pero solo tienen un par de inconvenientes en determinadas circunstancias.

Después de que ninguna de las soluciones propuestas realmente funcionara, logré esto agregando un change_multiple change_multiple. Esto solo funcionará para RSpec 3, y no 2. *

 module RSpec module Matchers def change_multiple(receiver=nil, message=nil, &block) BuiltIn::ChangeMultiple.new(receiver, message, &block) end alias_matcher :a_block_changing_multiple, :change_multiple alias_matcher :changing_multiple, :change_multiple module BuiltIn class ChangeMultiple < Change private def initialize(receiver=nil, message=nil, &block) @change_details = ChangeMultipleDetails.new(receiver, message, &block) end end class ChangeMultipleDetails < ChangeDetails def actual_delta @actual_after = [@actual_after].flatten @actual_before = [@actual_before].flatten @actual_after.map.with_index{|v, i| v - @actual_before[i]} end end end end end 

ejemplo de uso:

 it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do a = "." * 4 b = "." * 10 times_called = 0 expect { times_called += 1 a += ".." b += "-----" }.to change_multiple{[a.length, b.length]}.by([2,5]) expect(times_called).to eq(1) end 

Hacer que by_at_least y by_at_most funcionen para change_multiple requeriría un trabajo adicional.