Outside In Tdd With Chefspec

This is the fifth part of a series on the development of Chef Broiler Plate, in which we go over setting up a robust, TDD framework for Chef cookbook development.

Shameless Self Promotion

With this post, I'd like to announce that the companion book, The Chef Survival Guide has been published on Leanpub. Why not head over and pick it up to see the rest of this series and all future examples and updates!? With a minimum price of $9.99, you can get a condensed dose of Chef best practices, saving you untold hours of internet scouring and banging your head on the desk.

Back to Business

Previously, we finished our basic setup tasks and should now have a nicely set up development environment with Knife, Vagrant, a Rake build system, with continuous integration with Travis. All this is well and good, but the fun part of building this framework is working through a sample cookbook in a clean, test driven fashion.

To give you an idea of what's coming up, the testing tools we are going to use in this framework are:

  • Chefspec
  • Knife cookbook testing
  • Foodcritic
  • Minitest

Along the way, we will need to flesh out the framework to support these various things in a continuously integrated and test driven way, but the best way to do it is to work a feature. Consider the business requirement:

In order to let my developers know who's server this is
As an Admin
I want my company name in the message of the day.

Ok, so we are going to build a little message of the day cookbook; it will feed off a data bag value, and when we SSH into our box, we should see something like:

####################################################
#                                                  #
#  This server is property of Never Stop Building  #
#                                                  #
####################################################

But being true craftsmen, we will start by using Chefspec to "spec" out the cookbook!

Update your Gemfile

Add Chefspec to your Gemfile:

source "http://rubygems.org"

ruby '1.9.3'

gem 'rake', '10.0.4'
gem 'chef', '11.4.4'
gem "chefspec", "1.0.0"

group :development do
end

And, of course, do a bundle update.

Automate Chefspec Cookbook Creation

According to the Chefspec documentation, you can create a cookbook, and then use another command to create the Spec directory:

knife cookbook create -o . my_new_cookbook
knife cookbook create_specs -o . my_new_cookbook

Two commands simply won't do, and who knows how many other things I may want to do while creating a new cookbook. So, let's build a Rake task: new_cookbook

desc "Creates a new cookbook."
task :new_cookbook, :name do |t, args|
  sh "bundle exec knife cookbook create #{args.name}"
  sh "bundle exec knife cookbook create_specs #{args.name}"
end

Pretty simple for now.

Create Your Sample Cookbook (WOO!)

rake new_cookbook motd

At this point, we have a new blank cookbook. We can run the basic tests with

rspec cookbooks

But that is lame… let's add a Rake task for that and update our build task to use it:

desc "Runs chefspec on all the cookbooks."
task :chefspec do
  sh "bundle exec rspec cookbooks"
end

# Make sure to add the below line to your build task:
Rake::Task[:chefspec].execute

Not too hard.

Create a Failing Spec

Finally, we can add the Spec for the MOTD cookbook, in the motd/spec/default_spec.rb file:

require 'chefspec'

describe 'motd::default' do
  let (:chef_run) { ChefSpec::ChefRunner.new.converge 'motd::default' }
  it 'should do create a motd file with the company name' do
    Chef::Recipe.any_instance.stub(:data_bag_item).with("global-properties", "company").and_return({"id" => "company", "name" => "Never Stop Building"})
    expect(chef_run).to create_file_with_content('/etc/motd.tail', 'This server is property of Never Stop Building')
  end
end

Running rake build should show us this now fails. Next, we need to implement the cookbook.

Make your Spec Pass

For the recipes/default.rb file:

data_bag_item = data_bag_item('global-properties', 'company')

template "/etc/motd.tail" do
  source "motd.tail.erb"
  mode "0644"

  company_name = data_bag_item['name']
  filler = "#" * company_name.length
  space_filler = " " * company_name.length

  variables(
    :company_name => company_name,
    :filler => filler,
    :space_filler => space_filler
  )
  action :create
end

And the templates/default/motd.tail.erb file:

##############################<%= @filler %>###
#                             <%= @space_filler %>  #
#  This server is property of <%= @company_name %>  #
#                             <%= @space_filler %>  #
##############################<%= @filler %>###

Awesome! We made a test driven Cookbook! And, because our Travis file is already referencing the build task, when we push this code up, our CI will run this test and verify our work.

Coming up…

More testing to come! In the next post, we will learn about using "Foodcritic" to test our cookbook code quality.

If you enjoyed reading this or learned something, please consider sharing via , , or . Thanks!