[英]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
功能來做到這一點,但我真的看不到只allow
或expect
的方法。 我知道我可以模擬現有實例和 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.