1

私は次のモデルを持っています

class User < ActiveRecord::Base
  before_create :set_some_values

  private
  def set_some_values
    #do something
  end
end

仕様では、Fabrication gemを使用してオブジェクトを作成していますが、メソッドをスタブする方法が見つかりませんset_some_values。私は試した

User.any_instance.stub!(:set_some_values).and_return(nil)

しかし、製造はこれを無視しているようです。することは可能ですか?

4

2 に答える 2

2

これが、私が ActiveRecord コールバックを好まない理由です。なぜなら、コールバックとは何の関係も持ちたくない場合 (たとえば、コールバック内で外部サービスを呼び出しているため) について心配する必要があるからです。それをスタブします。はい、コールバック内でメソッドをスタブ化することもできますが、それは同じ問題であり、実際には少し悪いです。なぜなら、メソッド内で何もしたくない何かが心配になっているからです。

いつものように、ここには複数のオプションがあります。

私が過去によく使ったオプションの 1 つは、デフォルトでオフにする条件をコールバックに追加することです。したがって、Post クラスは次のようになります。

class Post
  before_save :sync_with_store, :if => :syncing_with_store?

  def syncing_with_store?; @syncing_with_store; end
  attr_writer :syncing_with_store

  def sync_with_store
     # make an HTTP request or something
  end
end

これで、コールバックを本当に呼び出したい場所 (おそらくコントローラー内またはどこでも) で、post.syncing_with_store = trueを呼び出す前に設定できますpost.save

このアプローチの欠点は、あなた (およびあなたと協力している他の開発者) が心に留めておかなければならないことであり、これを行う必要があることは実際には明らかではありません。一方、これを忘れても何も悪いことはありません。

別のオプションは、偽のクラスを使用することです。保存時にデータを外部データ ストアにプッシュする Post があるとします。でアクセスできる別のクラス (Pusher など) にプッシュするコードを抽出できますPost.pusher_service。ただし、デフォルトでは、これは同じインターフェースに応答するが何もしない偽の Pusher クラスに設定されます。以下のようなので:

class Post
  class << self
    attr_accessor :pusher_service
  end
  self.pusher_service = FakePostPusher

  before_save :sync_with_store

  def sync_with_store
    self.class.pusher_service.run(self)
  end
end

class FakePostPusher
  def self.run(post)
    new(post).run
  end

  def initialize(post)
    @post = post
  end

  def run
    # do nothing
  end
end

class PostPusher < FakePostPusher
  def run
    # actually make the HTTP request or whatever
  end
end

本番環境ファイルでは、Post.pusher_service = Pusher. 個々のテストまたはテスト ケースでは、Post のサブクラスを作成し、 -- let(:klass) { Class.new(Post) }-- 設定klass.pusher_service = Pusherします (そうすれば、永続的に設定して将来のテストに影響を与えることはありません)。

私が実験してきた 3 番目のアプローチは次のとおりです。単純に ActiveRecord コールバックを使用しないでください。これは、 Gary Bernhardt のスクリーンキャスト(ちなみに、これはかなり素晴らしいものです)から拾ったものです。代わりに、投稿を作成する行為をラップするサービス クラスを定義します。何かのようなもの:

class PostCreator
  def self.run(attrs={})
    new(attrs).run
  end

  def initialize(attrs={})
    @post = Post.new(attrs)
  end

  def run
    if @post.save
      make_http_request
      return true
    else
      return false
    end
  end

  def make_http_request
    # ...
  end
end

この方法PostCreator.run(attrs)は、Post を経由するのではなく、事実上、投稿を作成する方法です。Post 内で保存をテストするために、コールバックをスタブ化する必要はありません。PostCreator プロセスをテストしたい場合、魔法のようなことは何もありません。必要なメソッドを簡単にスタブ化するか、個別にテストすることができます。(ここでメソッドをスタブ化することは、AR コールバックをスタブ化することと同じであると主張することができますが、何が起こっているのかをより明確にしていると思います。)明らかに、これは投稿の作成のみを処理しますが、投稿の更新についても同じことができます。

とにかく、さまざまなアイデア、あなたの毒を選んでください。

于 2012-05-17T17:36:38.967 に答える
0

この #set_some_values メソッドは、レコードに対して #save を呼び出すと呼び出されます。したがって、コンストラクターとは関係がないため、 User.any_instance をスタブする必要はありません。次のように、レコードを作成してから部分スタブを実行するだけです。

record.stub(:set_some_values)
record.save
于 2012-05-15T17:03:27.727 に答える