簡體   English   中英

RSpec:對於在我調用的函數中實例化的對象,我怎么能不使用`allow_any_instance_of`?

[英]RSpec: How can I not use `allow_any_instance_of` for objects that get instantiated in the functions I call?

我有一個 class A和一個方法M ,我想為其編寫一個測試T 問題是方法M創建了一個新的 object O 我想模擬新的 object O的方法F

class A

  def M(p1, p2)
    @o = O.new(p1, p2)
  end

end

class O

  def F(q)
    ...
  end

end

我可以很容易地使用 RSpec 的allow_any_instance_of功能來做到這一點,但我真的看不到只allowexpect的方法。 我知道我可以模擬現有實例和 class 的方法,但從我的測試來看,我無法讓它對在我正在測試的方法中創建的對象的方法起作用。

T :process do
  it "works" do
    # This works
    allow_any_instance_of(O).to receive(:F).and_return(123)
    ...
  end

  it "does not works" do
    # This fails
    allow(O).to receive(:F).and_return(123)
    ...
  end
end

我怎么知道它失敗了?

我用puts()改變了我的F方法,當我使用allow(O)時,我可以在屏幕上看到 output 。 當我使用allow_any_instance_of()時,它根本不會出現。 所以我知道它只在后者中按預期工作。

  def F(q)
    puts("If I see this, then F() was not mocked properly.")
    ...
  end

我認為allow(O)...應該連接到 class 所以每當創建一個新實例時,模擬函數都會跟隨,但顯然不是。

您是否有 RSpec 測試以不涉及使用allow_any_instance_of() function 的不同方式處理此類 mocking 案例?

我問的原因是因為它自 RSpec 3.3 以來被標記為過時@allow-old-syntax )所以聽起來我們不應該再使用這個功能了,特別是一旦 RSpec 4.x 出來,它可能會消失。

這個原因

allow(O).to receive(:F).and_return(123)

不起作用的是:F不是O的方法,因此O永遠不會收到此消息(方法調用)。

對您來說最好的解決方案是重構代碼以使用依賴注入。 (請注意,如果您提供了一個現實生活中的示例 - 更接近地面 - 可能會進行一些更好的重構,那么您的示例是極端抽象的)

class A
  attr_accessor :o_implementation

  def initialize(o_implementation)
    @o_implementation = o_implementation
  end

  def M(p1, p2)
    @o = o_implementation.new(p1, p2)
  end

end

RSpec.describe A do
  subject { described_class.new(klass) }
  let(:klass) { O }
  let(:a_double) { instance_double(klass) }


  it do 
     allow(klass).to receive(:new).and_return(a_mock)
     allow(a_double).to receive(:F).and_return(123)
  end
end

通過依賴注入,您可以超越 class 實例化的決定。 這將您的代碼解耦(A 停止與 O 耦合,現在它僅取決於它正在使用的 O 接口),並使測試更容易*。

(*) 有人可能會爭辯說allow_any_instance更容易(涉及更少,打字更少),但它有一些問題,應該盡可能避免。

(順便說一句:我可以理解可能需要對您的代碼進行非常徹底的匿名化,但您仍然可以遵循 ruby 樣式指南:方法以小寫字母開頭,只有類以大寫字母開頭)

所以首先: allow(O)有效,但只會捕獲 class 方法。 如果您需要捕獲實例方法,則需要為特定實例調用allow

由於您的示例非常稀疏,我看不出我們為什么不能從測試中拆分 object 的創建? 如果可能的話,一個非常簡單的方法是編寫如下內容:

describe :process do 
  before do 
    @o = A.o_maker(p1,p2) 
    allow(@o).to receive(:some_function) { 123 } 
  end 
  it "works" do 
    # do something with `@o` that should call the function
  end
end 

如前所述,我個人更喜歡這種方法而不是創建模擬 class。 這可能是眾所周知的,但為了清楚起見:模擬 class 恕我直言的問題是您不再測試 class A而是模擬。 這在某些情況下可能很有用,但從最初的問題來看,不清楚它是否適用於這種情況,以及這是否不是不必要的復雜。 其次:如果您的代碼如此復雜(例如,某些創建新 object 然后調用F的方法),我寧願 1)重構我的代碼以使其可測試,和/或 2)測試副作用(例如F添加審核日志行,設置 state,...)。 我不需要“測試”我的實現(是否調用了正確的方法),但它是否被執行(當然,一如既往,有例外,例如在調用外部服務或其他東西時——但所有這些都是不可能推斷出來的從原始問題)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM