Using shared examples and shared context in infrastructure testing
During the last few months we've been heavily adding unit testing to our Chef codebase. In this article I will explain the benefits and how to use shared_examples and shared_context in ChefSpec testing.
ChefSpec isn't anything more than an extension of RSpec for unit infrastructure testing. We have all the resources available to express assertions that RSpec provides, I will focus on two of them.
Shared Examples
According to the official documentation:
Shared examples let you describe behaviour of types or modules. When declared, a shared group's content is stored. It is only realized in the context of another example group, which provides any context the shared group needs to run.
You can use a shared example to group common assertions that can be reused for different use cases in your test suite. There are two relevant pieces of syntax used:
- To define the shared example you just use
shared_examples
and pass a block. - To include a shared example in your testing you can use one of the following keywords:
include_examples 'name' # include the examples in the current context matching metadata # include the examples in the current context it_behaves_like 'name' # include the examples in a nested context it_should_behave_like 'name' # include the examples in a nested context
Let's try a small example: assume that all of your servers require a few base packages installed. For the sake of this example let's say that it's just htop and nmap. But your web servers also require postgresql-9.5 and your database servers also require postgresql.
shared_examples 'base server' do
it 'has htop package installed' do
expect(chef_run).to install_apt_package('htop')
end
it 'has nmap package installed' do
expect(chef_run).to install_apt_package('nmap')
end
end
describe 'mycookbook::web' do
let(:chef_run) do
ChefSpec::SoloRunner.converge(described_recipe)
end
it_behaves_like 'base server'
it 'has nginx package installed' do
expect(chef_run).to install_apt_package('nginx')
end
end
describe 'mycookbook::database' do
let(:chef_run) do
ChefSpec::SoloRunner.converge(described_recipe)
end
it_behaves_like 'base server'
it 'has postgresql-9.5 package installed' do
expect(chef_run).to install_apt_package('postgresql-9.5')
end
end
As the example illustrates, this is a good way of expressing common behavior for your test suite. Go to the official RSpec documentation for a complete explanation.
Shared Context
We head back to the official definition for the current explanation for a shared context:
Use shared_context to define a block that will be evaluated in the context of example groups either locally, using include_context in an example group, or globally using config.include_context.
We use shared context to reuse an inital state of the test that different sets of assertions can use later.
To declare a shared context you need two keywords:
- Use
shared_context
and pass a block to declare the context. - Use
include_context
to include the context in a block.
For our example we'll assume that our tests for different recipes need a common set of Chef node attributes, a databag, and to memorize the value of an FQDN.
shared_context 'test_server' do
let(:fqdn) { 'srv0.example.com' }
runner = ChefSpec::ServerRunner.new do |node, server|
node.normal['nginx']['version'] = '1.2.3'
node.automatic['network']['default_interface'] = 'eth0'
server.create_data_bag('network', { 'eth0' => '192.168.1.2' })
server.update_node(node)
end
runner.converge(described_recipe)
end
Then we can reuse this series of settings in different group of tests
describe 'mycookbook::web' do
include_context 'test_server'
...
end
describe 'mycookbook::database' do
include_context 'test_server'
...
end
There is an alternative way of declaring shared contexts via matching metadata. You can learn more about shared contexts by going to the official docs.
Summary
Shared context and examples are a good, DRY way of reusing useful information for your tests and to reuse the tests themselves. You can take advantadge of both to have a cleaner, more efficient, and wider test suite.
Jacobo García López de Araujo
Devops, infrastructure, urban cyclist, music nerd.
We think domain management should be easy.
That's why we continue building DNSimple.
4.3 out of 5 stars.
Based on Trustpilot.com and G2.com reviews.