wake-up-neo.com

Rückrufe auf Factory Girl und Rspec überspringen

Ich teste ein Modell mit einem After-Create-Callback, das ich beim Testen nur gelegentlich ausführen möchte. Wie kann ich Rückrufe von einer Fabrik aus überspringen/ausführen?

class User < ActiveRecord::Base
  after_create :run_something
  ...
end

Fabrik:

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    ...
    # skip callback

    factory :with_run_something do
      # run callback
  end
end
87
luizbranco

Ich bin nicht sicher, ob es die beste Lösung ist, aber ich habe dies mit folgendem Erfolg erreicht:

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

Läuft ohne Rückruf:

FactoryGirl.create(:user)

Laufen mit Rückruf:

FactoryGirl.create(:user_with_run_something)
104
luizbranco

Wenn Sie keinen Rückruf ausführen möchten, gehen Sie wie folgt vor:

User.skip_callback(:create, :after, :run_something)
Factory.create(:user)

Beachten Sie, dass skip_callback nach seiner Ausführung für andere Spezifikationen persistent ist. Beachten Sie daher Folgendes:

before do
  User.skip_callback(:create, :after, :run_something)
end

after do
  User.set_callback(:create, :after, :run_something)
end
79
Minimul

Keine dieser Lösungen ist gut. Sie entstellen die Klasse, indem sie Funktionen entfernen, die aus der Instanz entfernt werden sollten, nicht aus der Klasse.

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

Anstatt den Rückruf zu unterdrücken, unterdrücke ich die Funktionalität des Rückrufs. In gewisser Weise gefällt mir dieser Ansatz besser, weil er expliziter ist.

27
B Seven

Ich möchte die Antwort von @luizbranco verbessern, um after_save callback beim Erstellen anderer Benutzer wieder verwendbarer zu machen.

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

Läuft ohne after_save callback:

FactoryGirl.create(:user)

Läuft mit after_save callback:

FactoryGirl.create(:user, :with_after_save_callback)

In meinem Test ziehe ich es vor, Benutzer standardmäßig ohne Callback zu erstellen, da die verwendeten Methoden zusätzliche Elemente ausführen, die ich normalerweise in meinen Testbeispielen nicht möchte.

---------- UPDATE ------------.__: Ich habe die Verwendung von skip_callback beendet, da in der Testsuite einige Inkonsistenzprobleme aufgetreten sind.

Alternative Lösung 1 (Verwendung von Stub und 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

Alternative Lösung 2 (mein bevorzugter Ansatz):

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
24
konyak

Ein einfacher Stub hat in Rspec 3 am besten funktioniert

allow(User).to receive_messages(:run_something => nil)
5
samg

Diese Lösung funktioniert für mich und Sie müssen Ihrer Factory-Definition keinen zusätzlichen Block hinzufügen:

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

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

Der Aufruf von skip_callback aus meiner Fabrik war für mich problematisch.

In meinem Fall habe ich vor und nach dem Erstellen eine Dokumentenklasse mit einigen s3-bezogenen Callbacks, die ich nur ausführen möchte, wenn der vollständige Stack getestet werden muss. Ansonsten möchte ich die s3-Rückrufe überspringen.

Wenn ich in meiner Factory skip_callbacks ausprobierte, blieb dieser Callback-Sprung bestehen, selbst wenn ich ein Dokumentobjekt direkt erstellt habe, ohne eine Factory zu verwenden. Stattdessen habe ich Mocca-Stubs im After-Build-Aufruf verwendet und alles funktioniert perfekt:

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

Wichtiger Hinweis Sie sollten beide angeben. Wenn Sie zuvor nur mehrere Spezifikationen verwenden und ausführen, wird der Rückruf mehrmals deaktiviert. Es wird beim ersten Mal gelingen, beim zweiten wird Callback nicht mehr definiert. Also wird es einen Fehler geben 

3
AndreiMotinga

Dies funktioniert mit der aktuellen rspec-Syntax (ab diesem Beitrag) und ist viel sauberer:

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

Die Antwort von James Chevalier über das Überspringen des before_validation-Callbacks hat mir nicht geholfen.

im Modell:

before_validation :run_something, on: :create

in der Fabrik:

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

In Bezug auf die Antwort ( https://stackoverflow.com/a/35562805/2001785 ) müssen Sie den Code nicht zur Factory hinzufügen. Ich fand es einfacher, die Methoden in den Spezifikationen selbst zu überladen. Zum Beispiel anstelle von (in Verbindung mit dem Werkscode in der zitierten Stelle)

let(:user) { FactoryGirl.create(:user) }

Ich mag es (ohne den genannten Fabrikcode)

let(:user) do
  FactoryGirl.build(:user).tap do |u|
      u.define_singleton_method(:send_welcome_email){}
      u.save!
    end
  end
end

Auf diese Weise müssen Sie nicht nur die Werks- und die Testdatei betrachten, um das Verhalten des Tests zu verstehen.

2
user2001785

In meinem Fall muss der Callback etwas in meinen Redis-Cache laden. Aber dann hatte/wollte ich keine Redis-Instanz für meine Testumgebung. 

after_create :load_to_cache

def load_to_cache
  Redis.load_to_cache
end

Für meine Situation, ähnlich wie oben, habe ich gerade meine load_to_cache-Methode in meinem spec_helper, Mit:

Redis.stub(:load_to_cache)

In bestimmten Situationen, in denen ich dies testen möchte, muss ich sie lediglich im Vorher-Block der entsprechenden Rspec-Testfälle deaktivieren.

Ich weiß, dass in Ihrem after_create etwas komplizierteres passiert, oder Sie finden das nicht sehr elegant. Sie können versuchen, den in Ihrem Modell definierten Rückruf abzubrechen, indem Sie in Ihrer Factory einen after_create-Hook definieren (siehe factory_girl-Dokumente), in dem Sie wahrscheinlich denselben Rückruf definieren und false zurückgeben können, entsprechend dem Abschnitt 'Callbacks abbrechen' in diesem Abschnitt Artikel . (Ich bin nicht sicher, in welcher Reihenfolge der Rückruf ausgeführt wird, weshalb ich diese Option nicht gewählt habe).

(Leider kann ich den Artikel nicht finden.) Ruby ermöglicht Ihnen die Verwendung einiger Dirty-Meta-Programme, um einen Callback-Hook aufzuheben (Sie müssen ihn zurücksetzen). Ich denke, das wäre die am wenigsten bevorzugte Option.

Nun, es gibt noch eine weitere Sache, nicht wirklich eine Lösung, aber sehen Sie, ob Sie Factory.build in Ihren Spezifikationen verwenden können, anstatt das Objekt tatsächlich zu erstellen. (Wäre am einfachsten, wenn du kannst).

2
jake

Rails 5 - skip_callback löst beim Überspringen aus einer FactoryBot-Factory einen Argumentfehler aus.

ArgumentError: After commit callback :whatever_callback has not been defined

Es gab einen Wechsel in Rails 5 wie skip_callback nicht erkannte Rückrufe behandelt:

ActiveSupport :: Callbacks # skip_callback löst jetzt einen ArgumentError aus, wenn ein nicht erkannter Callback entfernt wird

Wenn skip_callback ab Werk aufgerufen wird, ist der tatsächliche Rückruf im AR-Modell noch nicht definiert.

Wenn Sie alles ausprobiert und Ihre Haare wie ich herausgezogen haben, finden Sie hier Ihre Lösung (stammt aus der Suche nach FactoryBot-Problemen) ( HINWEIS: raise: false part ):

after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }

Fühlen Sie sich frei, es mit anderen Strategien zu verwenden, die Sie bevorzugen.

1
RudyOnRails

Ich habe festgestellt, dass die folgende Lösung sauberer ist, da der Rückruf auf Klassenebene ausgeführt wird.

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