ruby on rails - has_many - Saltar devoluciones de llamada en Factory Girl y Rspec
factory bot rails associations (13)
Cuando no desee ejecutar una devolución de llamada haga lo siguiente:
User.skip_callback(:create, :after, :run_something)
Factory.create(:user)
Tenga en cuenta que skip_callback será persistente en otras especificaciones después de su ejecución, por lo tanto, considere algo como lo siguiente:
before do
User.skip_callback(:create, :after, :run_something)
end
after do
User.set_callback(:create, :after, :run_something)
end
Estoy probando un modelo con una devolución de llamada posterior a la creación que me gustaría ejecutar solo en algunas ocasiones durante la prueba. ¿Cómo puedo omitir / ejecutar devoluciones de llamada desde una fábrica?
class User < ActiveRecord::Base
after_create :run_something
...
end
Fábrica:
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
...
# skip callback
factory :with_run_something do
# run callback
end
end
En mi caso, tengo la devolución de llamada cargando algo a mi redis caché. Pero luego no tuve / quiero que se ejecute una instancia de redis para mi entorno de prueba.
after_create :load_to_cache
def load_to_cache
Redis.load_to_cache
end
Para mi situación, similar a la anterior, acabo de load_to_cache
mi método load_to_cache
en mi spec_helper, con:
Redis.stub(:load_to_cache)
Además, en cierta situación en la que quiero probar esto, solo tengo que quitarlos en el bloque anterior de los casos de prueba Rspec correspondientes.
Sé que es posible que algo más complicado ocurra en tu after_create
o que esto no sea muy elegante. Puede intentar cancelar la devolución de llamada definida en su modelo, definiendo un gancho after_create
en su Fábrica (consulte factory_girl docs), donde probablemente pueda definir la misma devolución de llamada y devolver false
, de acuerdo con la sección ''Cancelando devoluciones de llamada'' de este article . (No estoy seguro del orden en el que se ejecutan las devoluciones de llamadas, por lo que no opté por esta opción).
Por último, (lo siento, no puedo encontrar el artículo), Ruby te permite utilizar una meta programación sucia para desenganchar un gancho de devolución de llamada (deberás restablecerlo). Supongo que esta sería la opción menos preferida.
Bueno, hay una cosa más, realmente no es una solución, pero ve si puedes salir con Factory.build en tus especificaciones, en lugar de crear el objeto. (Sería el más simple si puedes).
Encontré que la siguiente solución es una manera más limpia ya que la devolución de llamada se ejecuta / establece en un nivel de clase.
# create(:user) - will skip the callback.
# create(:user, skip_create_callback: false) - will set the callback
FactoryBot.define do
factory :user do
first_name "Luiz"
last_name "Branco"
transient do
skip_create_callback true
end
after(:build) do |user, evaluator|
if evaluator.skip_create_callback
user.class.skip_callback(:create, :after, :run_something)
else
user.class.set_callback(:create, :after, :run_something)
end
end
end
end
Esta solución funciona para mí y no tiene que agregar un bloque adicional a su definición de fábrica:
user = FactoryGirl.build(:user)
user.send(:create_without_callbacks) # Skip callback
user = FactoryGirl.create(:user) # Execute callbacks
Esto funcionará con la sintaxis rspec actual (a partir de esta publicación) y es mucho más limpio:
before do
User.any_instance.stub :run_something
end
La respuesta de James Chevalier acerca de cómo omitir la devolución de llamada previa a la validación no me ayudó, así que si te rebasas igual que yo, aquí está la solución de trabajo:
en modelo:
before_validation :run_something, on: :create
en fábrica:
after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }
Llamar skip_callback desde mi fábrica resultó problemático para mí.
En mi caso, tengo una clase de documento con algunas devoluciones de llamada relacionadas con s3 en antes y después de crear que solo quiero ejecutar cuando es necesario probar la pila completa. De lo contrario, quiero omitir esas devoluciones de llamadas S3.
Cuando probé skip_callbacks en mi fábrica, persistía la omisión de devolución de llamada incluso cuando creaba un objeto de documento directamente, sin utilizar una fábrica. Así que, en cambio, utilicé los stubs de mocha en la llamada posterior a la compilación y todo funciona a la perfección:
factory :document do
upload_file_name "file.txt"
upload_content_type "text/plain"
upload_file_size 1.kilobyte
after(:build) do |document|
document.stubs(:name_of_before_create_method).returns(true)
document.stubs(:name_of_after_create_method).returns(true)
end
end
Me gustaría mejorar la respuesta de @luizbranco para hacer que la devolución de llamada after_save sea más reutilizable al crear otros usuarios.
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
#...
after(:build) { |user|
user.class.skip_callback(:create,
:after,
:run_something1,
:run_something2)
}
trait :with_after_save_callback do
after(:build) { |user|
user.class.set_callback(:create,
:after,
:run_something1,
:run_something2)
}
end
end
end
Funcionando sin la devolución de llamada after_save:
FactoryGirl.create(:user)
Corriendo con la devolución de llamada after_save:
FactoryGirl.create(:user, :with_after_save_callback)
En mi prueba, prefiero crear usuarios sin la devolución de llamada de forma predeterminada porque los métodos utilizados ejecutan cosas extra que normalmente no quiero en mis ejemplos de prueba.
---------- ACTUALIZACIÓN ------------ Deje de usar skip_callback porque hubo algunos problemas de inconsistencia en el conjunto de pruebas.
Solución alternativa 1 (uso de stub y unstub):
after(:build) { |user|
user.class.any_instance.stub(:run_something1)
user.class.any_instance.stub(:run_something2)
}
trait :with_after_save_callback do
after(:build) { |user|
user.class.any_instance.unstub(:run_something1)
user.class.any_instance.unstub(:run_something2)
}
end
Solución alternativa 2 (mi enfoque preferido):
after(:build) { |user|
class << user
def run_something1; true; end
def run_something2; true; end
end
}
trait :with_after_save_callback do
after(:build) { |user|
class << user
def run_something1; super; end
def run_something2; super; end
end
}
end
Ninguna de estas soluciones es buena. Borran la clase eliminando la funcionalidad que debe eliminarse de la instancia, no de la clase.
factory :user do
before(:create){|user| user.define_singleton_method(:send_welcome_email){}}
En lugar de suprimir la devolución de llamada, estoy suprimiendo la funcionalidad de la devolución de llamada. En cierto modo, me gusta más este enfoque porque es más explícito.
No estoy seguro de si es la mejor solución, pero lo he logrado con éxito utilizando:
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
#...
after(:build) { |user| user.class.skip_callback(:create, :after, :run_something) }
factory :user_with_run_something do
after(:create) { |user| user.send(:run_something) }
end
end
end
Funcionando sin devolución de llamada:
FactoryGirl.create(:user)
Corriendo con devolución de llamada:
FactoryGirl.create(:user_with_run_something)
Un simple stub funcionó mejor para mí en Rspec 3
allow(User).to receive_messages(:run_something => nil)
FactoryGirl.define do
factory :order, class: Spree::Order do
trait :without_callbacks do
after(:build) do |order|
order.class.skip_callback :save, :before, :update_status!
end
after(:create) do |order|
order.class.set_callback :save, :before, :update_status!
end
end
end
end
Nota importante que debe especificar ambos. Si solo usa antes y ejecuta varias especificaciones, intentará desactivar la devolución de llamada varias veces. Lo logrará la primera vez, pero en el segundo, la devolución de llamada ya no se definirá. Entonces se equivocará
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
#...
after(:build) { |user| user.class.skip_callback(:create, :after, :run_something) }
trait :user_with_run_something do
after(:create) { |user| user.class.set_callback(:create, :after, :run_something) }
end
end
end
Puede establecer la devolución de llamada con un rasgo para esas instancias cuando desee ejecutarlo.