Chefspec Custom Matchers for LWRP’s

I’ve been getting serious about adding proper testing to my Chef cookbooks lately. I previously mentioned a wrapper cookbook that I’m working on that uses an LWRP from a library cookbook I wrote. I haven’t put the library cookbook out in the public because it’s fairly narrowly scoped and there’s probably something else out there that does the same thing.

So my recipe looks like this:

include_recipe 'heat-nginx::default'
 
heat_nginx_vhost node[:heat_deploy][:fqdn] do
  server_name node[:heat_deploy][:fqdn]
  listen 80
  ssl false
  locations({
    '/' => {
      'root' => node[:heat_deploy][:web_root]
    }
  })
  access_log "/var/log/nginx/deploy-access.log"
  error_log "/var/log/nginx/deploy-error.log"
  action [:create, :enable]
  notifies :reload, 'service[nginx]', :delayed
end

My spec looks like this:

require_relative '../spec_helper'
 
describe 'heat_deploy::default' do
  let(:chef_run) do
    runner = ChefSpec::Runner.new(step_into: ['heat_nginx_vhost'])
    runner.node.set[:heat_deploy][:fqdn] = 'deploy.test'
    runner.node.set[:heat_deploy][:web_root] = '/srv/deploy'
    runner.converge(described_recipe)
  end
 
  let(:nginx_config) do
    'server {
    server_name deploy.test;
 
    listen 80;
 
    location / {
        root /srv/deploy;
    }
 
    access_log /var/log/nginx/deploy-access.log custom_log;
    error_log /var/log/nginx/deploy-error.log;
}
'
  end
 
  it 'includes recipe heat_nginx::default' do
    expect(chef_run).to include_recipe('heat-nginx::default')
  end
 
  context 'configuring nginx virtual host' do
    it 'creates heat_nginx_vhost[deploy.test]' do
      expect(chef_run).to create_heat_nginx_vhost('deploy.test')
    end
 
    it 'steps into heat_nginx_vhost and creates template[/etc/nginx/sites-available/deploy.test]' do
      expect(chef_run).to render_file('/etc/nginx/sites-available/deploy.test').with_content(nginx_config)
    end
 
    it 'eanbles heat_nginx_vhost[deploy.test]' do
      expect(chef_run).to enable_heat_nginx_vhost('deploy.test')
    end
 
    it 'notifies service[nginx] to reload delayed' do
      resource = chef_run.heat_nginx_vhost('deploy.test')
      expect(resource).to notify('service[nginx]').to(:reload).delayed
    end
  end
end

To make the references to the heat_nginx_vhost resource work, I created libraries/matchers.rb in the heat-nginx cookbook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# heat-nginx/libraries/matchers.rb
 
if defined?(ChefSpec)
  ChefSpec::Runner.define_runner_method(:heat_nginx_vhost)
 
  def create_heat_nginx_vhost(resource)
    ChefSpec::Matchers::ResourceMatcher.new(:heat_nginx_vhost, :create, resource)
  end
 
  def enable_heat_nginx_vhost(resource)
    ChefSpec::Matchers::ResourceMatcher.new(:heat_nginx_vhost, :enable, resource)
  end
 
  def delete_heat_nginx_vhost(resource)
    ChefSpec::Matchers::ResourceMatcher.new(:heat_nginx_vhost, :delete, resource)
  end
 
end

On line 4, I’m defining a method for the Runner so that chef_run.heat_nginx_vhost('deploy.test') is a thing, which allows me to check that it has indeed notified service[nginx] to reload.

The other methods defined enabled me to be able to say expect(chef_run).to create_heat_nginx_vhost('deploy.test').

Note that I’m running chefspec on my wrapper cookbook and it’s pulling those in from the heat-nginx cookbook. I think it’s pretty neat, but something that, unfortunately, isn’t documented terribly clearly.

2 thoughts on “Chefspec Custom Matchers for LWRP’s

  1. Thanks for posting this, needed to do this today to match some LWRP’s – nice to see defining a matcher is pretty straightforward.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>