テスト中に特定の機会にのみ実行したいafter createコールバックを使用してモデルをテストしています。工場からコールバックをスキップ/実行するにはどうすればよいですか?
class User < ActiveRecord::Base
after_create :run_something
...
end
工場:
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
...
# skip callback
factory :with_run_something do
# run callback
end
end
それが最善の解決策であるかどうかはわかりませんが、次を使用してこれを達成できました。
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
コールバックなしで実行:
FactoryGirl.create(:user)
コールバックで実行する:
FactoryGirl.create(:user_with_run_something)
コールバックを実行したくない場合は、次を実行します。
User.skip_callback(:create, :after, :run_something)
Factory.create(:user)
Skip_callbackは、実行後に他の仕様を超えて持続することに注意してください。したがって、次のようなものを検討してください。
before do
User.skip_callback(:create, :after, :run_something)
end
after do
User.set_callback(:create, :after, :run_something)
end
これらの解決策はどれも優れていません。クラスからではなく、インスタンスから削除すべき機能を削除することにより、クラスを汚します。
factory :user do
before(:create){|user| user.define_singleton_method(:send_welcome_email){}}
コールバックを抑制する代わりに、コールバックの機能を抑制しています。ある意味では、このアプローチはより明示的であるため、このアプローチのほうが好きです。
@luizbrancoの回答を改善して、他のユーザーを作成するときにafter_saveコールバックをより再利用できるようにします。
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
After_saveコールバックなしで実行:
FactoryGirl.create(:user)
After_saveコールバックで実行する:
FactoryGirl.create(:user, :with_after_save_callback)
私のテストでは、デフォルトではコールバックなしでユーザーを作成することを好みます。なぜなら、使用されるメソッドは、テスト例では通常は望まない余分なものを実行するからです。
---------- UPDATE ------------テストスイートにいくつかの矛盾の問題があったため、skip_callbackの使用を停止しました。
代替ソリューション1(スタブとアンスタブの使用):
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
代替ソリューション2(私の推奨アプローチ):
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
このソリューションは私のために機能し、ファクトリ定義に追加のブロックを追加する必要はありません。
user = FactoryGirl.build(:user)
user.send(:create_without_callbacks) # Skip callback
user = FactoryGirl.create(:user) # Execute callbacks
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
重要な注意両方を指定する必要があります。 beforeのみを使用して複数の仕様を実行する場合、コールバックを複数回無効にしようとします。初回は成功しますが、2回目では、コールバックはもう定義されません。だからエラーになります
私の工場からskip_callbackを呼び出すと、問題が発生することがわかりました。
私の場合、作成の前後にいくつかのs3関連のコールバックを含むドキュメントクラスがあり、完全なスタックのテストが必要な場合にのみ実行する必要があります。それ以外の場合は、これらのs3コールバックをスキップします。
ファクトリでskip_callbacksを試したとき、ファクトリを使用せずにドキュメントオブジェクトを直接作成した場合でも、コールバックスキップが持続しました。その代わりに、ビルド後の呼び出しでモカスタブを使用し、すべてが完全に機能しています。
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
skip_callback
FactoryBotファクトリからスキップすると引数エラーが発生します。ArgumentError: After commit callback :whatever_callback has not been defined
change in Rails 5 があり、skip_callbackが認識されないコールバックを処理する方法がありました。
ActiveSupport :: Callbacks#skip_callbackは、認識されないコールバックが削除された場合、ArgumentErrorを発生させるようになりました
いつ skip_callback
はファクトリーから呼び出され、ARモデルの実際のコールバックはまだ定義されていません。
すべてを試してみて、私のように髪を引っ張ったら、ここにあなたの解決策があります (FactoryBotの問題の検索からそれを得た) (注raise: false
part):
after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }
あなたが好む他の戦略と一緒にそれを自由に使用してください。
これは(この投稿の時点で)現在のrspec構文で動作し、はるかにクリーンです:
before do
User.any_instance.stub :run_something
end
Before_validationコールバックをスキップする方法についてのJames Chevalierの回答は私を助けませんでした。
モデル内:
before_validation :run_something, on: :create
工場で:
after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }
上記の回答については、 https://stackoverflow.com/a/35562805/2001785 の場合、コードを工場に追加する必要はありません。仕様自体のメソッドをオーバーロードする方が簡単だとわかりました。たとえば、代わりに(引用された投稿の工場コードと組み合わせて)
let(:user) { FactoryGirl.create(:user) }
私は使用するのが好きです(引用された工場コードなし)
let(:user) do
FactoryGirl.build(:user).tap do |u|
u.define_singleton_method(:send_welcome_email){}
u.save!
end
end
end
この方法では、テストの動作を理解するためにファクトリとテストファイルの両方を調べる必要はありません。
私の場合、コールバックでredisキャッシュに何かをロードしています。しかし、その後、テスト環境用に実行されているredisインスタンスを持っていませんでした。
after_create :load_to_cache
def load_to_cache
Redis.load_to_cache
end
私の状況では、上記と同様に、spec_helperでload_to_cache
メソッドをスタブしました。
Redis.stub(:load_to_cache)
また、これをテストしたい特定の状況では、対応するRspecテストケースの前のブロックでそれらをスタブ解除する必要があります。
あなたはあなたのafter_create
でもっと複雑なことが起こっているかもしれないし、非常にエレガントだとは思わないかもしれません。 Factoryでafter_create
フックを定義することにより、モデルで定義されたコールバックをキャンセルしようとすることができます(factory_girlドキュメントを参照)。おそらく、同じコールバックを定義してfalse
を返すことができますこの 記事 の「コールバックのキャンセル」セクション。 (コールバックが実行される順序はわかりません。このため、このオプションを選択しませんでした)。
最後に、(申し訳ありませんが記事を見つけることができません)Rubyいくつかのダーティメタプログラミングを使用してコールバックフックを解除できます(リセットする必要があります)。最も優先度の低いオプション。
まあ、実際には解決策ではありませんが、もう1つありますが、実際にオブジェクトを作成する代わりに、仕様でFactory.buildを使用できるかどうかを確認してください。 (可能な場合は最も簡単になります)。
コールバックはクラスレベルで実行/設定されるため、次のソリューションがよりクリーンな方法であることがわかりました。
# 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