Saturday, January 18, 2014

ChefSpec: Automatically resolve resource matchers for third party cookbooks

ChefSpec is an awesome unit testing framework for testing your chef recipes. However, when I first started using it, I was surprised to find that it didn't automatically resolve resource matchers for third party cookbooks.

For example, this works because the "package" resource is built into Chef:


describe 'example::default' do
  let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }

  it 'does something' do
    expect(chef_run).to install_package('apache2')
  end
end

However, this will not, since the "windows" cookbook is a third party (external) cookbook:

describe 'example::default' do
  let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }

  it 'does something' do
    expect(chef_run).to run_windows_batch('script')
  end
end

Instead, you will receive a message like this:

NoMethodError: undefined method `run_windows_batch' for #
./spec/default_spec.rb:28:in `block (2 levels) in '

The documentation does have a workaround. Define each resource at /spec/helpers/matchers.rb:

# spec/support/matchers.rb
def my_custom_matcher(resource_name)
  ChefSpec::Matchers::ResourceMatcher.new(:windows_batch, :run, resource_name)
end

This works, although at the time of this writing, most cookbooks do not define custom matchers, so you're going to be writing quite a few of these. IMHO, custom matchers on the cookbook level seems like it's a bit of a maintenance problem, considering this information is readily available in your cookbook collection anyway. So I ended up writing a helper that will automatically resolve any LWRP matcher in your specs. This is how you can take advantage of it:


require 'chefspec'
require_relative('chefspec_extensions/automatic_resource_matcher') #or wherever you decide to put it

describe 'example::default' do
  let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }

  it 'does something' do
    expect(chef_run).to run_windows_batch('script')
  end
end

Now your test will pass as expected.

The source code for the matcher can be found here.

Please note that this does incur a slight performance hit. The extension will scan your cookbooks directory and look for resources that match action_your_cookbook_your_lwrp. However, in the grand scheme of things, ChefSpec is already somewhat slow due to the nature of how Chef works. In my opinion, the extra overhead is negligible (likely in the millisecond range), and completely worth it. Additionally, this has NOT been tested with Berkshelf or Librarian at the present time. If you would like to see it supported, feel free to leave a comment and I'll look into it.

Resources:

https://github.com/lynx44/chefspec_extensions