Testing Capistrano Recipes For Dummies

A couple months ago, I took the lead of capifony, a set of Capistrano recipes for Symfony2 projects. I tried to revamp the project, and one of my goals was to add tests. This article sums up my work on that topic.

Making your recipe testable

In order to make a Capistrano recipe testable, a common practice is to create a module that can extends a Capistrano configuration instance:

module MyRecipe
  def self.load_into(configuration)
    configuration.load do
      # Your code here
      # ...
    end
  end
end

if Capistrano::Configuration.instance
  MyRecipe.load_into(Capistrano::Configuration.instance)
end

The last three lines are needed to keep the original behavior of your recipe, assuming you have an existing recipe that doesn’t follow this practice.

You are ready to test your recipe, but how? capistrano-spec to the rescue! capistrano-spec integrates RSpec into the Capistrano world so that you can easily test your recipes. It has been created by Josh Nichols.

Installing capistrano-spec

The first step is to create a spec/spec_helper.rb file used to load everything to run your tests. This file looks like:

# Bundler
require 'rubygems'
require 'bundler/setup'

$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))

require 'capistrano-spec'
require 'rspec'
require 'rspec/autorun'

# Add capistrano-spec matchers and helpers to RSpec
RSpec.configure do |config|
  config.include Capistrano::Spec::Matchers
  config.include Capistrano::Spec::Helpers
end

# Require your lib here
require 'my_recipe'

As you may noticed, Bundler is used to manage our dependencies. The Gemfile file should contain:

source 'http://rubygems.org'

gemspec

gem 'rake'
gem 'rspec'
gem 'capistrano-spec', :git => 'git://github.com/mydrive/capistrano-spec.git'

gemspec loads all dependencies located in the .gemspec file, used to build your gem. For more information, please read Using Bundler with Rubygem gemspecs.

Also, a best practice here is to rename your Gemfile to .gemfile as it’s used for testing purpose only. It’s not a big deal though, it just makes your project a bit cleaner.

Run the command below to install your dependencies:

BUNDLE_GEMFILE=.gemfile bundle install

You also need a Rakefile file to configure Rake. It is similar to make and allows you to run a set of tasks. In order to run your examples, you need a spec task. The following snippet is all you need:

require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec) do |spec|
  spec.rspec_opts = '--color --format=documentation -I lib -I spec'
  spec.pattern = "spec/**/*_spec.rb"
end

task :default => :spec

Now, you probably want to write your first test, and you are right!

Writing your first test

Create a new file named spec/my_recipe_spec.rb and add the content below:

require 'spec_helper'

describe "MyRecipe" do
  before do
    @configuration = Capistrano::Configuration.new
    @configuration.extend(Capistrano::Spec::ConfigurationExtension)

    MyRecipe.load_into(@configuration)
  end

  # Your code here
end

As you can see, we create a Capistrano configuration instance, and we extend it with capistrano-spec. This configuration instance is injected into our module.

Try to run the spec task using the following command:

BUNDLE_GEMFILE=.gemfile bundle exec rake spec

You should see:

Finished in 0.00006 seconds
0 examples, 0 failures

If everything looks good, congratulations! Time to write your examples. Yes, in Rspec, you don’t really write tests, you write executable examples of the expected behavior of your code.

First, declare the @configuration variable as subject:

subject { @configuration }

It tells RSpec what we are doing the tests on. Instead of writing something like:

it { @configuration.should have_run('pwd') }

You will be able to write:

it { should have_run('pwd') }

It’s way more readable.

Now, you can start your first context:

context "when running my:command" do
  before do
    @configuration.find_and_execute_task('my:command')
  end

  it { should have_run('echo "Hello, World!"') }
end

A context block always starts with either When or With, and should describe one feature in a given context. In this context, we try to find and execute the task my:command, and to ensure it has run echo "Hello, World!".

If this task has some parameters, like a :name variable, you should write a new context to test it. The @configuration object has all methods available in your recipe, so you can call the set method to change a parameter:

context "when running my:command" do
  before do
    @configuration.set :name, "John"
    @configuration.find_and_execute_task('my:command')
  end

  it { should have_run('echo "Hello, John!"') }
end

Other matchers are available like have_gotten, or have_uploaded depending on what you need. Look at the capistrano-spec readme for more information, it also contains useful examples. That’s all folks.

By the way, if you found a typo, please fork and edit this post. Thank you so much! This post is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

If you like this post or if you use one of the Open Source projects I maintain, say hello by email. There is also my Amazon Wish List. Thank you ♥

Comments

Fork me on GitHub