单元测试厨师自定义资源时模拟库函数
Mocking library functions when unit testing chef custom resource
我在 Chef 中创建了一个非常简单的自定义资源,该资源内部是一些简单的逻辑。有问题的逻辑调用了一些自定义辅助方法。
我可以构建资源,并将其作为配方的一部分执行 - 但如果我想对资源本身内的行为进行单元测试以确保流程正确。因此,我希望能够模拟那些辅助函数的行为,以便我可以指导资源行为。不幸的是,我无法让它工作。
我的食谱是这样的:
my_resource 'executing' do
action :execute
end
资源如下所示:
action :execute do
if my_helper?(node['should_be_true'])
converge_by "Updating" do
my_helper_method
end
end
end
action_class do
include CustomResource::Helpers
end
功能简单:
module CustomResource
module Helpers
def my_helper?(should_be_true)
should_be_true
end
def my_helper_method
hidden_method
end
def hidden_method
3
end
end
end
当我尝试在 ChefSpec 测试中模拟它们的行为时,出现错误:
it 'executes' do
allow(CustomResource::Helpers).to receive(:my_helper?).and_return(true)
expect(CustomResource::Helpers).to receive(:my_helper_method)
expect { chef_run }.to_not raise_error
end
Failure/Error: expect(CustomResource::Helpers).to receive(:my_helper_method)
(CustomResource::Helpers).my_helper_method(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
知道我在嘲笑中做错了什么吗?
提前致谢!
不幸的是,考虑到 ChefSpec 的工作原理,这非常困难。您需要将它存根在资源的任何实例上,因为那是实际的接收者。如果您可以改用模块方法,那会稍微容易一些。我认为您已经 require_relative
-ing 您的库文件,否则代码会以不同的方式失败。但是,对于自定义资源无法做到这一点,因为它们是 DSL-y 并且 Ruby 无法直接加载它们。最简单的选择通常是不测试这种东西。将代码放入检查节点属性是否已设置的辅助方法中,如果已设置则使用该值(具体取决于具体情况),然后在测试中设置该值。虽然它很恶心而且很烦人,但这也是石盐存在的部分原因。
设法通过更改模拟方法来完成此工作...这些模块函数已添加到 action_class 中,因此在运行时它们是资源 ActionClass 的特定实例上的方法。不确定我的解决方案是否 right/ideal - 但它确实有效:
include CustomResource::Helpers
<snip>
it 'executes' do
allow_any_instance_of(Chef::Resource::ActionClass).to receive(:my_helper?).and_return(true)
expect_any_instance_of(Chef::Resource::ActionClass).to receive(:my_helper_method)
expect { chef_run }.to_not raise_error
end
我确实考虑过避免使用 'any instance of' 模拟,但后来我遇到了问题并放弃了 - 显然 ActionClass 有很多我不想担心的行为。
我在 Chef 中创建了一个非常简单的自定义资源,该资源内部是一些简单的逻辑。有问题的逻辑调用了一些自定义辅助方法。
我可以构建资源,并将其作为配方的一部分执行 - 但如果我想对资源本身内的行为进行单元测试以确保流程正确。因此,我希望能够模拟那些辅助函数的行为,以便我可以指导资源行为。不幸的是,我无法让它工作。
我的食谱是这样的:
my_resource 'executing' do
action :execute
end
资源如下所示:
action :execute do
if my_helper?(node['should_be_true'])
converge_by "Updating" do
my_helper_method
end
end
end
action_class do
include CustomResource::Helpers
end
功能简单:
module CustomResource
module Helpers
def my_helper?(should_be_true)
should_be_true
end
def my_helper_method
hidden_method
end
def hidden_method
3
end
end
end
当我尝试在 ChefSpec 测试中模拟它们的行为时,出现错误:
it 'executes' do
allow(CustomResource::Helpers).to receive(:my_helper?).and_return(true)
expect(CustomResource::Helpers).to receive(:my_helper_method)
expect { chef_run }.to_not raise_error
end
Failure/Error: expect(CustomResource::Helpers).to receive(:my_helper_method)
(CustomResource::Helpers).my_helper_method(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
知道我在嘲笑中做错了什么吗?
提前致谢!
不幸的是,考虑到 ChefSpec 的工作原理,这非常困难。您需要将它存根在资源的任何实例上,因为那是实际的接收者。如果您可以改用模块方法,那会稍微容易一些。我认为您已经 require_relative
-ing 您的库文件,否则代码会以不同的方式失败。但是,对于自定义资源无法做到这一点,因为它们是 DSL-y 并且 Ruby 无法直接加载它们。最简单的选择通常是不测试这种东西。将代码放入检查节点属性是否已设置的辅助方法中,如果已设置则使用该值(具体取决于具体情况),然后在测试中设置该值。虽然它很恶心而且很烦人,但这也是石盐存在的部分原因。
设法通过更改模拟方法来完成此工作...这些模块函数已添加到 action_class 中,因此在运行时它们是资源 ActionClass 的特定实例上的方法。不确定我的解决方案是否 right/ideal - 但它确实有效:
include CustomResource::Helpers
<snip>
it 'executes' do
allow_any_instance_of(Chef::Resource::ActionClass).to receive(:my_helper?).and_return(true)
expect_any_instance_of(Chef::Resource::ActionClass).to receive(:my_helper_method)
expect { chef_run }.to_not raise_error
end
我确实考虑过避免使用 'any instance of' 模拟,但后来我遇到了问题并放弃了 - 显然 ActionClass 有很多我不想担心的行为。