k6ps
k6ps

Reputation: 369

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

I have the following Chef recipe:

# 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

and another recipe

# 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

Now i want to write a Chefspec unit test to the default recipe in isolation. So i wrote this:

# 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

How do i create a dummy of the service defined in another_recipe to prevent this happening:

 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?)

I know that this is probably a bad design and a fairly simple newbie question, but i'm really stuck here and my situation is this:

Thanks :) k6ps

Upvotes: 4

Views: 2529

Answers (3)

Marcel Tricolici
Marcel Tricolici

Reputation: 440

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

Upvotes: 1

nshemonsky
nshemonsky

Reputation: 1

Another way would be to include a begin/rescue in your recipe so the service is added to the resource collection if necessary. The default action is :nothing so you don't need anything more than to list it in the rescue block.

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

Upvotes: 0

Tensibai
Tensibai

Reputation: 15784

My point of view:

You should allow the another_recipe to be in run and do the necessary for it to converge. If not you can't really trust your test as they're not done against what will happen in a run.

Workaround to answer your case anyway:

Well you may add a "mockers recipe" in your cookbook which will define no-ops resources you need to test your recipe without too much stub/mock calls.

Lets say it's called 'spec-receipe.rb' and it looks like:

service 'my-service' do
  action :nothing
end

Then you run your tests including this 'spec-recipe' like this:

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

Another way could be to include_recipe 'my_cookbook::spec-recipe' if defined?(ChefSpec) so this recipe would be included only in a chefspec run and not in normal run and you don't have to specify it in the runner declaration.

Upvotes: 3

Related Questions