web-dev-qa-db-ja.com

Factory GirlとRspecのコールバックをスキップ

テスト中に特定の機会にのみ実行したい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
95
luizbranco

それが最善の解決策であるかどうかはわかりませんが、次を使用してこれを達成できました。

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)
107
luizbranco

コールバックを実行したくない場合は、次を実行します。

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
81
Minimul

これらの解決策はどれも優れていません。クラスからではなく、インスタンスから削除すべき機能を削除することにより、クラスを汚します。

factory :user do
  before(:create){|user| user.define_singleton_method(:send_welcome_email){}}

コールバックを抑制する代わりに、コールバックの機能を抑制しています。ある意味では、このアプローチはより明示的であるため、このアプローチのほうが好きです。

33
B Seven

@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
25
konyak

このソリューションは私のために機能し、ファクトリ定義に追加のブロックを追加する必要はありません。

user = FactoryGirl.build(:user)
user.send(:create_without_callbacks) # Skip callback

user = FactoryGirl.create(:user)     # Execute callbacks
6
auralbee

Rspec 3では、シンプルなスタブが最適でした

allow(User).to receive_messages(:run_something => nil)
5
samg
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回目では、コールバックはもう定義されません。だからエラーになります

5
AndreiMotinga

私の工場から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
4
uberllama

Rails 5-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) }

あなたが好む他の戦略と一緒にそれを自由に使用してください。

4
RudyOnRails

これは(この投稿の時点で)現在のrspec構文で動作し、はるかにクリーンです:

before do
   User.any_instance.stub :run_something
end
3
Zyren

Before_validationコールバックをスキップする方法についてのJames Chevalierの回答は私を助けませんでした。

モデル内:

before_validation :run_something, on: :create

工場で:

after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }
3

上記の回答については、 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

この方法では、テストの動作を理解するためにファクトリとテストファイルの両方を調べる必要はありません。

2
user2001785

私の場合、コールバックで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を使用できるかどうかを確認してください。 (可能な場合は最も簡単になります)。

2
jake

コールバックはクラスレベルで実行/設定されるため、次のソリューションがよりクリーンな方法であることがわかりました。

# 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
1
Sairam