如何创建在另一个配方中定义的虚拟资源而不在测试 运行 中包含另一个配方?

How to create a dummy resource defined in another recipe without including the other recipe in test run?

我有以下厨师食谱:

# recipes/default.rb

include_recipe 'my-cookbook::another_recipe'

execute 'Do some stuff' do
  command "echo some stuff"
  action :run
end

template "my_dir/my_file.txt" do
  source "my_file.txt.erb"
  owner 'myuser'
  group 'mygroup'
  notifies :restart, resources(:service => 'my-service'), :delayed
end

和另一个食谱

# recipes/another_recipe.rb

service 'my-service' do
  start_command "my-service start"
  stop_command "my-service stop"
  supports :start => true, :stop => true
  action :nothing
end

现在我想单独为 default 配方编写 Chefspec 单元测试。所以我写了这个:

# spec/unit/recipes/default_spec.rb

require 'rspec/core'
require 'chefspec'

describe 'my-cookbook::default' do
  let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }

  before do
    allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('my-cookbook::another_recipe')
  end

  it "Does the stuff" do
    expect(chef_run).to run_execute("echo some stuff")
  end

end

如何为 another_recipe 中定义的服务创建虚拟对象以防止这种情况发生:

 11:    group 'mygroup'
 12>>   notifies :restart, resources(:service => 'my-service'), :delayed
 13:  end
...
Failure/Error: let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
Chef::Exceptions::ResourceNotFound:
Cannot find a resource matching service[my-service] (did you define it first?)

我知道这可能是一个糟糕的设计和一个相当简单的新手问题,但我真的被困在这里,我的情况是这样的:

谢谢 :) k6ps

我的观点:

您应该允许 another_recipe 在 运行 中并进行必要的收敛。如果不是,你就不能真正相信你的测试,因为他们没有针对 运行.

中会发生的事情进行测试。

无论如何都要回答您的问题的解决方法:

好吧,你可以在你的食谱中添加一个 "mockers recipe",它将定义你需要的无操作资源来测试你的食谱,而不需要太多 stub/mock 调用。

假设它叫做 'spec-receipe.rb',看起来像:

service 'my-service' do
  action :nothing
end

然后你 运行 你的测试包括这个 'spec-recipe' 像这样:

let(:chef_run) { ChefSpec::SoloRunner.converge('my_cookbook::spec-recipe',described_recipe) }

另一种方法是 include_recipe 'my_cookbook::spec-recipe' if defined?(ChefSpec) 这样这个食谱将只包含在 chefspec 运行 中而不是普通 运行 并且你不必在运行纳尔声明。

另一种方法是在您的配方中包含 begin/rescue,以便在必要时将服务添加到资源集合中。默认操作是 :nothing 所以你只需要在救援块中列出它。

begin
  svc = resources('service[my-service]')
rescue
  svc = service 'my-service'
end

template "my_dir/my_file.txt" do
  source "my_file.txt.erb"
  owner 'myuser'
  group 'mygroup'
  notifies :restart, svc, :delayed
end
describe 'mycookbook::default' do
  context 'something' do
    let(:solo) do
      ChefSpec::SoloRunner.new
    end 

    let(:chef_run) do
      solo.converge(described_recipe) do
        solo.resource_collection.insert(
                Chef::Resource::Service.new('apache2', solo.run_context))
      end 
    end 

    it 'runs without errors' do
      allow_any_instance_of(Chef::Recipe).to receive(:include_recipe)
      chef_run
    end 
  end 
end