rails has_many cheatsheet cheat bot attributes_for ruby-on-rails rspec factory-bot

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.