Je teste un modèle avec un rappel après création que je voudrais exécuter uniquement à certaines occasions lors du test. Comment puis-je ignorer / exécuter des rappels depuis une usine?
class User < ActiveRecord::Base
after_create :run_something
...
end
Usine:
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
...
# skip callback
factory :with_run_something do
# run callback
end
end
ruby-on-rails
rspec
factory-bot
Luizbranco
la source
la source
:on => :create
validation, utilisezafter(:build) { |user| user.class.skip_callback(:validate, :create, :after, :run_something) }
Class.skip_callback
appel sera persistant dans d'autres tests, donc si vos autres tests s'attendent à ce que le rappel se produise, ils échoueront si vous essayez d'inverser la logique de rappel de saut.after(:build)
bloc. Cela permet à votre usine d'exécuter le rappel par défaut et ne nécessite pas de réinitialiser le rappel après chaque utilisation.Lorsque vous ne souhaitez pas exécuter de rappel, procédez comme suit:
Sachez que skip_callback sera persistant dans les autres spécifications après son exécution, considérez donc quelque chose comme ce qui suit:
la source
Aucune de ces solutions n'est bonne. Ils dégradent la classe en supprimant les fonctionnalités qui doivent être supprimées de l'instance, pas de la classe.
Au lieu de supprimer le rappel, je supprime la fonctionnalité du rappel. D'une certaine manière, j'aime mieux cette approche car elle est plus explicite.
la source
around_*
(par exempleuser.define_singleton_method(:around_callback_method){|&b| b.call }
).J'aimerais apporter une amélioration à la réponse de @luizbranco pour rendre le rappel after_save plus réutilisable lors de la création d'autres utilisateurs.
Exécution sans rappel after_save:
Exécution avec le rappel after_save:
Dans mon test, je préfère créer des utilisateurs sans le rappel par défaut car les méthodes utilisées exécutent des éléments supplémentaires que je ne veux normalement pas dans mes exemples de test.
---------- MISE À JOUR ------------ J'ai arrêté d'utiliser skip_callback car il y avait des problèmes d'incohérence dans la suite de tests.
Solution alternative 1 (utilisation de stub et unstub):
Solution alternative 2 (mon approche préférée):
la source
Rails 5 -
skip_callback
erreur d'argument de levée lors du saut d'une usine FactoryBot.Il y a eu un changement dans Rails 5 avec la façon dont skip_callback gère les rappels non reconnus:
Lorsqu'il
skip_callback
est appelé depuis l'usine, le véritable rappel dans le modèle AR n'est pas encore défini.Si vous avez tout essayé et que vous vous êtes arraché les cheveux comme moi, voici votre solution (obtenue en recherchant des problèmes FactoryBot) ( NOTEZ la
raise: false
partie ):N'hésitez pas à l'utiliser avec les autres stratégies que vous préférez.
la source
Cette solution fonctionne pour moi et vous n'avez pas besoin d'ajouter un bloc supplémentaire à votre définition d'usine:
la source
Un simple talon fonctionnait mieux pour moi dans Rspec 3
la source
User
;:run_something
n'est pas une méthode de classe.Remarque importante, vous devez spécifier les deux. Si vous utilisez uniquement avant et exécutez plusieurs spécifications, il essaiera de désactiver le rappel plusieurs fois. Cela réussira la première fois, mais la seconde, le rappel ne sera plus défini. Donc ça va faire une erreur
la source
L'appel de skip_callback depuis mon usine s'est avéré problématique pour moi.
Dans mon cas, j'ai une classe de document avec des rappels liés à s3 avant et après la création que je ne veux exécuter que lorsque le test de la pile complète est nécessaire. Sinon, je veux ignorer ces rappels s3.
Lorsque j'ai essayé skip_callbacks dans mon usine, cela a persisté dans ce saut de rappel même lorsque j'ai créé un objet document directement, sans utiliser de fabrique. Donc à la place, j'ai utilisé des stubs moka dans l'appel après la construction et tout fonctionne parfaitement:
la source
before_validation
crochet (essayant de faireskip_callback
avec l'une des FactoryGirlbefore
ou desafter
options pourbuild
etcreate
n'a pas fonctionné)Cela fonctionnera avec la syntaxe rspec actuelle (à partir de ce post) et est beaucoup plus propre:
la source
La réponse de James Chevalier sur la façon d'ignorer le rappel before_validation ne m'a pas aidé, donc si vous traitez comme moi, voici une solution de travail:
dans le modèle:
en usine:
la source
Model.skip_callback(...)
Dans mon cas, j'ai le rappel qui charge quelque chose dans mon cache redis. Mais alors je n'avais pas / je ne voulais pas qu'une instance redis s'exécute pour mon environnement de test.
Pour ma situation, similaire à celle ci-dessus, j'ai juste stubé ma
load_to_cache
méthode dans mon spec_helper, avec:De plus, dans certaines situations où je veux tester cela, je dois juste les décoller dans le bloc avant des cas de test Rspec correspondants.
Je sais que vous pourriez avoir quelque chose de plus compliqué qui se passe
after_create
ou que vous ne trouverez pas cela très élégant. Vous pouvez essayer d'annuler le rappel défini dans votre modèle, en définissant unafter_create
hook dans votre Factory (reportez-vous à la documentation de factory_girl), où vous pouvez probablement définir un même rappel et retourfalse
, selon la section `` Annulation des rappels '' de cet article . (Je ne suis pas sûr de l'ordre dans lequel les rappels sont exécutés, c'est pourquoi je n'ai pas choisi cette option).Enfin, (désolé je ne trouve pas l'article) Ruby vous permet d'utiliser une méta programmation sale pour décrocher un crochet de rappel (vous devrez le réinitialiser). Je suppose que ce serait l'option la moins préférée.
Eh bien, il y a encore une chose, pas vraiment une solution, mais voyez si vous pouvez vous en sortir avec Factory.build dans vos spécifications, au lieu de créer réellement l'objet. (Ce serait le plus simple si vous le pouvez).
la source
Concernant la réponse postée ci-dessus, https://stackoverflow.com/a/35562805/2001785 , vous n'avez pas besoin d'ajouter le code à l'usine. J'ai trouvé plus facile de surcharger les méthodes dans les spécifications elles-mêmes. Par exemple, au lieu de (en conjonction avec le code d'usine dans l'article cité)
J'aime utiliser (sans le code d'usine cité)
De cette façon, vous n'avez pas besoin de regarder à la fois l'usine et les fichiers de test pour comprendre le comportement du test.
la source
J'ai trouvé que la solution suivante était un moyen plus propre puisque le rappel est exécuté / défini au niveau de la classe.
la source
Voici un extrait que j'ai créé pour gérer cela de manière générique.
Il ignorera tous les rappels configurés, y compris les rappels liés aux rails comme
before_save_collection_association
, mais il n'en sautera pas certains nécessaires pour que ActiveRecord fonctionne correctement, comme lesautosave_associated_records_for_
rappels générés automatiquement .puis plus tard:
Inutile de dire, YMMV, alors regardez dans les journaux de test ce que vous sautez vraiment. Peut-être avez-vous un petit bijou qui ajoute un rappel dont vous avez vraiment besoin et que vos tests échouent lamentablement ou à partir de votre modèle de 100 rappels, vous avez juste besoin d'un couple pour un test spécifique. Pour ces cas, essayez le transitoire
:force_callbacks
PRIME
Parfois, vous devez également ignorer les validations (le tout dans un effort pour accélérer les tests), puis essayez avec:
la source
Vous pouvez simplement définir le rappel avec un trait pour ces instances lorsque vous souhaitez l'exécuter.
la source