⚠️ This content has been written a long time ago. As such, it might not reflect my current thoughts anymore. I keep this page online because it might still contain valid information.
Testing Capistrano recipes for dummies
Clermont-Fd Area, France2022-04-18 // I proofread this article and fixed some links.
A couple months ago, I took the lead of capifony, a set of Capistrano recipes for Symfony projects. I tried to revamp the project and one of my goals was to add tests. This article presents 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.
Now you are ready to test your recipe, but how? capistrano-spec to the rescue! capistrano-spec brings RSpec to 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 should look like this:
# 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 have noticed, Bundler is used to manage the dependencies. The
Gemfile
file should contain the following content:
source 'http://rubygems.org'
gemspec
gem 'rake'
gem 'rspec'
gem 'capistrano-spec'
gemspec
loads all the dependencies located in the .gemspec
file, which is
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
because
it is used for testing purposes only. It’s not a big deal but it 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 it allows to run a set of tasks written in Ruby. In order to run your test
files (“examples” in RSpec), 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 file, and you are right! Let’s do it!
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
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 behaviors of your code.
First, declare the @configuration
variable as subject:
subject { @configuration }
It tells RSpec what we are going to test. 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 ensure it has run echo "Hello, World!"
.
If this task takes parameters like a :name
variable, you should write a new
context
to test it. The @configuration
object has all its methods available
in your recipe. 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!
Links
- Better Specs: RSpec guidelines with Ruby
- capistrano-spec: helpers and matchers for testing capistrano recipes
- capifony specs: the capifony test files
Feel free to fork and edit this post if you find a typo, thank you so much! This post is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
Comments
No comments here. You can get in touch with me on Mastodon or send me an email if you prefer.