it-swarm.com.de

Wie kann man die Callback-Methode des Modells unabhängig testen?

Ich hatte eine Methode in einem Modell:

class Article < ActiveRecord::Base
  def do_something
  end
end

Ich hatte auch einen Komponententest für diese Methode:

# spec/models/article_spec.rb
describe "#do_something" do
  @article = FactoryGirl.create(:article)
  it "should work as expected" do
    @article.do_something
    expect(@article).to have_something
  end
  # ...several other examples for different cases
end

Alles war in Ordnung, bis ich herausfand, dass es besser ist, diese Methode in einen after_save-Callback zu verschieben:

class Article < ActiveRecord::Base
  after_save :do_something

  def do_something
  end
end

Nun sind alle meine Tests über diese Methode gebrochen. Ich muss es reparieren durch:

  • Kein spezifischer Aufruf von do_something, da create oder save diese Methode ebenfalls auslösen wird oder ich doppelte Db-Aktionen treffe.
  • create in build ändern
  • Testen Sie respons_to
  • Verwenden Sie general model.save anstelle des einzelnen Methodenaufrufs model.do_something

    describe "#do_something" do
      @article = FactoryGirl.build(:article)
      it "should work as expected" do
        expect{@article.save}.not_to raise_error
        expect(@article).to have_something
        expect(@article).to respond_to(:do_something)
      end
    end
    

Der Test ist bestanden, aber meine Sorge ist nicht mehr die spezifische Methode. Der Effekt wird gemischt mit anderen Rückrufen sein, wenn hinzugefügt wird. 

Meine Frage ist, gibt es eine schöne Möglichkeit, die Instanzmethoden des Modells unabhängig zu testen, die zu einem Rückruf werden?

31
Billy Chan

Callback- und Callback-Verhalten sind unabhängige Tests. Wenn Sie einen after_save-Rückruf prüfen möchten, müssen Sie ihn als zwei Dinge betrachten:

  1. Wird der Rückruf für die richtigen Ereignisse ausgelöst?
  2. Ist die aufgerufene Funktion das Richtige?

Angenommen, Sie haben die Klasse Article mit vielen Rückrufen. So würden Sie Folgendes testen:

class Article < ActiveRecord::Base
  after_save    :do_something
  after_destroy :do_something_else
  ...
end

it "triggers do_something on save" do
  expect(@article).to receive(:do_something)
  @article.save
end

it "triggers do_something_else on destroy" do
  expect(@article).to receive(:do_something_else)
  @article.destroy
end

it "#do_something should work as expected" do
  # Actual tests for do_something method
end

Dies entkoppelt Ihre Rückrufe vom Verhalten. Sie könnten beispielsweise dieselbe Callback-Methode article.do_something auslösen, wenn ein anderes verwandtes Objekt aktualisiert wird, beispielsweise user.before_save { user.article.do_something }. Dies wird all diese aufnehmen.

Testen Sie Ihre Methoden also wie gewohnt. Sorgen Sie sich separat um die Rückrufe.

Edit: Tippfehler und mögliche Missverständnisse Edit: "etwas tun", um "etwas auszulösen"

64
Subhas

Sie können shoulda-callback-matchers verwenden, um die Existenz Ihrer Rückrufe zu testen, ohne sie anzurufen.

describe Article do
  it { should callback(:do_something).after(:save) }
end

Wenn Sie auch das Verhalten des Rückrufs testen möchten: 

describe Article do
  ...

  describe "#do_something" do
    it "gives the article something" do
      @article.save
      expect(@article).to have_something
    end
  end
end
14
Filip Bartuzi

Dies ist mehr ein Kommentar als eine Antwort, aber ich habe es hier für das Syntax-Highlighting eingefügt ...

Ich wollte eine Möglichkeit, die Rückrufe in meinen Tests zu überspringen. Dies ist, was ich getan habe. (Dies kann bei den durchgeführten Tests helfen.)

class Article < ActiveRecord::Base
  attr_accessor :save_without_callbacks
  after_save :do_something

  def do_something_in_db
    unless self.save_without_callbacks
      # do something here
    end
  end
end

# spec/models/article_spec.rb
describe Article do
  context "after_save callback" do
    [true,false].each do |save_without_callbacks|
      context "with#{save_without_callbacks ? 'out' : nil} callbacks" do
        let(:article) do
          a = FactoryGirl.build(:article)
          a.save_without_callbacks = save_without_callbacks
        end
        it do
          if save_without_callbacks
            # do something in db
          else
            # don't do something in db
          end
        end
      end
    end
  end
end
0
Teddy

Im Geiste von Sandi Metz und minimalistischem testing scheint mir der Vorschlag in https://stackoverflow.com/a/16678194/2001785 , um den Aufruf einer möglicherweise privaten Methode zu bestätigen, nicht richtig.

Das Prüfen eines öffentlich beobachtbaren Nebeneffekts oder das Bestätigen einer ausgehenden Befehlsnachricht ist für mich sinnvoller. Christian Rolle gab ein Beispiel an http://www.chrisrolle.com/de/blog/activerecord-callback-tests-with-rspec .

0
user2001785