如何避免使用 allow_any_instance_of?
How to avoid using allow_any_instance_of?
假设我们有以下代码:
class A
def create_server
options = {
name: NameBuilder.new.build_name
}
do_some_operations(options)
end
end
为了测试这些方法,我曾经使用 allow_any_instance_of
:
it 'does operations' do
allow_any_instance_of(NameBuilder).to receive(:build_name)
# test body
end
但是文档建议我们不要使用它because of several reasons。那么如何避免allow_any_instance_of
呢?我只有一个解决方案:
class A
def create_server
options = {
name: builder.build_name
}
do_some_operations
end
private
def builder
NameBuilder.new
end
end
但是使用这种方法,代码很快就会充满几乎无用的方法(尤其是当您在 class 中积极使用不同对象的组合时)。
如果很难测试,说明你的class设计有问题。在您的情况下,当您在 class 中对特定 class 的特定方法调用进行测试时,您正在这样测试:
allow_any_instance_of(NameBuilder).to receive(:build_name)
你的测试确切地知道该方法是如何在内部实现的。您的 classes 应该封装逻辑并将其隐藏。你做的恰恰相反。
您不应该测试任何内部方法逻辑。只需测试行为。提供输入并测试输出的正确性。
如果您真的想在 NameBuilder
class 上测试该方法调用,则注入该依赖项并使您的 class 更易于测试。这也遵循了 OOP 原则。
class A
def create_server(builder)
do_some_operations(name: builder.build_name)
end
end
在没有根据 进行依赖注入(我同意)的情况下,您还可以考虑取消对 NameBuilder.new
的调用,这样您就可以直接控制 [= 的实例12=] 正在测试中:
class NameBuilder
def build_name
# builds name...
end
end
class A
def create_server
options = {
name: NameBuilder.new.build_name
}
do_some_operations(options)
end
def do_some_operations(options)
# does some operations
end
end
RSpec.describe A do
let(:a) { described_class.new }
describe '#create_server' do
let(:name_builder) { instance_double(NameBuilder) }
before do
allow(NameBuilder).to receive(:new).and_return(name_builder)
end
it 'does operations' do
# the first expectation isn't really part of what you seem
# to want to test, but it shows that this way of testing can work
expect(name_builder).to receive(:build_name)
expect(a).to receive(:do_some_operations)
a.create_server
end
end
end
假设我们有以下代码:
class A
def create_server
options = {
name: NameBuilder.new.build_name
}
do_some_operations(options)
end
end
为了测试这些方法,我曾经使用 allow_any_instance_of
:
it 'does operations' do
allow_any_instance_of(NameBuilder).to receive(:build_name)
# test body
end
但是文档建议我们不要使用它because of several reasons。那么如何避免allow_any_instance_of
呢?我只有一个解决方案:
class A
def create_server
options = {
name: builder.build_name
}
do_some_operations
end
private
def builder
NameBuilder.new
end
end
但是使用这种方法,代码很快就会充满几乎无用的方法(尤其是当您在 class 中积极使用不同对象的组合时)。
如果很难测试,说明你的class设计有问题。在您的情况下,当您在 class 中对特定 class 的特定方法调用进行测试时,您正在这样测试:
allow_any_instance_of(NameBuilder).to receive(:build_name)
你的测试确切地知道该方法是如何在内部实现的。您的 classes 应该封装逻辑并将其隐藏。你做的恰恰相反。
您不应该测试任何内部方法逻辑。只需测试行为。提供输入并测试输出的正确性。
如果您真的想在 NameBuilder
class 上测试该方法调用,则注入该依赖项并使您的 class 更易于测试。这也遵循了 OOP 原则。
class A
def create_server(builder)
do_some_operations(name: builder.build_name)
end
end
在没有根据 NameBuilder.new
的调用,这样您就可以直接控制 [= 的实例12=] 正在测试中:
class NameBuilder
def build_name
# builds name...
end
end
class A
def create_server
options = {
name: NameBuilder.new.build_name
}
do_some_operations(options)
end
def do_some_operations(options)
# does some operations
end
end
RSpec.describe A do
let(:a) { described_class.new }
describe '#create_server' do
let(:name_builder) { instance_double(NameBuilder) }
before do
allow(NameBuilder).to receive(:new).and_return(name_builder)
end
it 'does operations' do
# the first expectation isn't really part of what you seem
# to want to test, but it shows that this way of testing can work
expect(name_builder).to receive(:build_name)
expect(a).to receive(:do_some_operations)
a.create_server
end
end
end