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
创建了一个新对象 O
。我想模拟那个新对象 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
或 [=24= 就可以做到这一点的方法].我知道我可以模拟现有实例和 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)
时,我可以在屏幕上看到该输出。当我使用 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()
函数的不同方式处理此类模拟案例?
我问的原因是因为它是 marked as obsolete (@allow-old-syntax
) 自 RSpec 3.3 以来,所以听起来我们不应该再使用此功能,尤其是一旦 RSpec4.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
更容易(更少涉及,更少打字),但它有 some issues,应尽可能避免使用。
(顺便说一句:我可以理解可能需要对代码进行非常彻底的匿名化,但您仍然可以遵循 ruby 风格指南:方法以小写字母开头,仅 classes 以大写开头)
所以首先:allow(O)
有效,但只会捕获 class 方法。如果需要捕获实例方法,则需要针对特定实例调用allow
。
由于您的示例非常稀疏,我看不出为什么我们不能将对象的创建与测试分开?如果可能的话,一个非常简单的方法是编写如下内容:
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 imho 的问题是您不再测试 class A
而是模拟。这在某些情况下可能有用,但从最初的问题来看,不清楚它是否适用于这种情况,以及这是否没有不必要的复杂性。其次:如果您的代码 是 如此复杂(例如,一些创建新对象然后调用 F
的方法),我宁愿 1) 重构我的代码以使其测试-able, and/or 2) 测试副作用(例如 F
添加审计日志行,设置状态,...)。我不需要 "test" 我的实现(是否调用了正确的方法),但它是否执行了(当然,一如既往,有例外,例如调用外部服务或其他东西时——但同样,所有这些都是不可能的从原来的问题中推导出来)。
我有一个 class A
和一个方法 M
,我想为其编写一个测试 T
。问题是方法 M
创建了一个新对象 O
。我想模拟那个新对象 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
或 [=24= 就可以做到这一点的方法].我知道我可以模拟现有实例和 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)
时,我可以在屏幕上看到该输出。当我使用 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()
函数的不同方式处理此类模拟案例?
我问的原因是因为它是 marked as obsolete (@allow-old-syntax
) 自 RSpec 3.3 以来,所以听起来我们不应该再使用此功能,尤其是一旦 RSpec4.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
更容易(更少涉及,更少打字),但它有 some issues,应尽可能避免使用。
(顺便说一句:我可以理解可能需要对代码进行非常彻底的匿名化,但您仍然可以遵循 ruby 风格指南:方法以小写字母开头,仅 classes 以大写开头)
所以首先:allow(O)
有效,但只会捕获 class 方法。如果需要捕获实例方法,则需要针对特定实例调用allow
。
由于您的示例非常稀疏,我看不出为什么我们不能将对象的创建与测试分开?如果可能的话,一个非常简单的方法是编写如下内容:
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 imho 的问题是您不再测试 class A
而是模拟。这在某些情况下可能有用,但从最初的问题来看,不清楚它是否适用于这种情况,以及这是否没有不必要的复杂性。其次:如果您的代码 是 如此复杂(例如,一些创建新对象然后调用 F
的方法),我宁愿 1) 重构我的代码以使其测试-able, and/or 2) 测试副作用(例如 F
添加审计日志行,设置状态,...)。我不需要 "test" 我的实现(是否调用了正确的方法),但它是否执行了(当然,一如既往,有例外,例如调用外部服务或其他东西时——但同样,所有这些都是不可能的从原来的问题中推导出来)。