web-dev-qa-db-ja.com

モデルのコールバックメソッドを個別にテストする方法

モデルにメソッドがありました:

class Article < ActiveRecord::Base
  def do_something
  end
end

このメソッドの単体テストもありました:

# 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

このメソッドをafter_saveコールバックに移動する方が良いとわかるまで、すべてがうまくいきました。

class Article < ActiveRecord::Base
  after_save :do_something

  def do_something
  end
end

これで、このメソッドに関するすべてのテストが壊れました。私はそれを修正しなければなりません:

  • createまたはsaveもこのメソッドをトリガーするため、do_somethingへの具体的な呼び出しはありません。そうでない場合は、dbアクションが重複します。
  • createbuildに変更します
  • Respond_toをテストします
  • 個々のメソッド呼び出しmodel.saveの代わりに一般的な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
    

テストは合格しましたが、私の懸念は、特定のメソッドに関するものではなくなったことです。効果は追加される場合は他のコールバックと混合されますになります。

私の質問は、モデルのインスタンスメソッドをテストする美しい方法はありますか独立してコールバックになることですか?

33
Billy Chan

コールバックとコールバックの動作は独立したテストです。 after_saveコールバックを確認する場合は、次の2つのことを考える必要があります。

  1. 適切なイベントに対してコールバックが発生していますか?
  2. 呼び出された関数は正しいことをしていますか?

Articleクラスに多くのコールバックがあると仮定します。これがテスト方法です。

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

これにより、コールバックが動作から切り離されます。たとえば、article.do_somethingなど、他の関連オブジェクトが更新されたときに同じコールバックメソッドuser.before_save { user.article.do_something }をトリガーできます。これにより、これらすべてが収容されます。

そのため、通常どおりメソッドをテストし続けます。コールバックを個別に心配します。

編集:タイプミスと潜在的な誤解編集:「何かをする」を「何かをトリガーする」に変更する

67
Subhas

shoulda-callback-matchers を使用して、コールバックを呼び出さずにテストできます。

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

コールバックの動作もテストする場合:

describe Article do
  ...

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

Sandi Metzとミニマリスト テスト の精神で、 https://stackoverflow.com/a/16678194/2001785 の提案はおそらくプライベートメソッドへの呼び出しを確認するために私には正しくないようです。

公に観察可能な副作用をテストするか、発信コマンドメッセージを確認することは、私にとってより理にかなっています。 Christian Rolleは http://www.chrisrolle.com/en/blog/activerecord-callback-tests-with-rspec で例を提供しました。

1
user2001785

これは答えというよりもコメントですが、構文を強調するためにここに配置します...

私はテストでコールバックをスキップする方法を望んでいました、これは私がやったことです。 (これは、壊れたテストに役立つ可能性があります。)

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