A fast way to write unit-tests for your Puppet code

A fast way to write unit-tests for your Puppet code Making unit tests for Puppet can be cumbersome. Making good unit tests for Puppet is not only cumbersome but also difficult and time-consuming. So it would be quite helpful to have some tools to help you with this task. In this blog post, we will show you one of the tools we use. It is called catalog-rspec.

What to test?

But one of the first questions you have to ask yourself is: “What should a unit test cover?”. If you have a manifest with only a simple set of resources, unit tests do not bring a lot of value. In that case, your unit test is a duplicate of your Puppet code. But if your Puppet code contains conditional logic like if and case statements, good unit tests become an invaluable tool.

Test approach

In the last decennium Test Driven Development, also known as TDD has gained a lot of followers. In essence, it means writing your test first, letting it fail, and then making the code needed to pass the test. So should you use it? The answer, like most of the times, is: It depends. We use TDD sometimes but surely not all the time. We find that the larger function of what you are trying to accomplish with Puppet is often a bigger driver of constructing and writing your Puppet code than the test of conditional logic. So that is why we sometimes tend to write the test after we have made the Puppet seemingly work.

catalog-rspec tool

We notice that it is sometimes difficult to get a mental view of how the Puppet catalog of your manifest looks. It would be a great help if somehow you could get a dump of the contents of your catalog to see if it contains the resources you’d expect.

The catalog-rspec tool does exactly that. The beauty of it is that it dumps the catalog in puppet RSpec code. So you can copy certain parts of it into your unit test.

Example workflow

Let’s create a Puppet manifest and some unit tests to give you a feel for how it works. Because I know all published Puppet code has good unit tests ;-), I will use a contrived example to show you how you can use it. The example is based on our Puppet learning module pizza. You can find a blog post about this module here. We are going to generate a new puppt module from scratch. If you want to, you can get the github repo containing all steps.

Use cases

We want to develop a puppet defined type to make it easy to define kid pizzas. The defined types allow to specify the age for kids and automatically create a pizza with the appropriate size for the kid.

Get going

In the next couple of paragraphs, we will take you on a journey with eight steps. In each small step, we will add a little setup, puppet code, or RSpec code to your project and make it a bit better. You can either type everything yourself (or copy paste) or you can use the githup repo we have setup. Every step is available in a specific git branch.

STEP ONE - generate the empty defined type and tests (code here)

We have already created the pizza_profile module with pdk new module. You can find the generated empty module here. Now let’s use the PDK to create the defined type:

$ pdk new defined_type kid_pizza

---------------Files added--------------
/Users/tutorial/pizza_profile/spec/defines/kid_pizza_spec.rb
/Users/tutorial/pizza_profile/manifests/kid_pizza.pp

----------------------------------------

As you can see, the PDK not only creates the defined type, it also creates an RSpec test. Let’s run this test.

$ pdk test unit --verbose
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format documentation
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}

pizza_profile::kid_pizza
  on centos-7-x86_64
    is expected to compile into a catalogue without dependency cycles

Finished in 0.48018 seconds (files took 2.81 seconds to load)
1 example, 0 failures

As you can see here, the PDK creates some generic tests. Tests that verify the catalog can compile without errors on all supported OS-es (in this case only centos-7). Although this is useful, it doesn’t test any conditional logic that is in the module.

STEP TWO - write the puppet code for the defined type (code here)

We have written this before. I some cases we do use TDD (Test Driven Development). In this case we write the Puppet code first. Here is the code:

#   pizza_profile::kid_pizza { 'namevar': }
define pizza_profile::kid_pizza (
  Integer[0,18] $age = 12,
  String[1]     $dough = 'white',   # Most kids don't like wholesome
  String[1]     $cheese = 'mozzarella',
) {
  if $age < 2 {
    fail "probably not wise to order a pizza for a kid of ${age}."
  } elsif $age < 5 {
    $size = 10
    $amount_of_tomato_souce = 4
    $amount_of_cheese = 4
  } elsif $age < 8 {
    $size = 20
    $amount_of_tomato_souce = 5
    $amount_of_cheese = 5
  } else {
    $size = 30
    $amount_of_tomato_souce = 6
    $amount_of_cheese = 5
  }

  crust { "${title}/medium_wholesome_thin_crust":
    ensure => baked,
    size   => $size,
    type   => 'thin',       # thin
    dough  => 'wholesome', # wholesome ore white
  }

  -> tomato_sauce { "${title}/thick_cristal":
    ensure    => 'present',
    type      => 'cristal',
    composure => 'thick',
    amount    => $amount_of_tomato_souce,
  }
  -> cheese { "${title}/a_lot_of_mozzarella":
    ensure => 'present',
    type   => $cheese,
    amount => $amount_of_cheese,
  }
}

As you can see, it contains some conditional logic based on the age of the kid. Because we’re using puppet custom types that define the pizza components, we must add this module and the dependency easy_type to the .fixtures.yaml file.

# This file can be used to install module dependencies for unit testing
# See https://github.com/puppetlabs/puppetlabs_spec_helper#using-fixtures for details
---
fixtures:
  forge_modules:
    pizza:     "enterprisemodules/pizza"
    easy_type: "enterprisemodules/easy_type"

If we run the default generated tests again:

$ pdk test unit
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format progress
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}
.

Finished in 0.46216 seconds (files took 3.15 seconds to load)
1 example, 0 failures

They are still passing. But none of the logic we use in the module is actually checked during the tests.

STEP THREE - Add catalog_rspec (code here)

To make it easy to write the required RSpec code, we are going to use the catalog_rspec gem. First we must add the definition of the gem to the Gemfile. Add this line to the bottom of your Gemfile

gem 'puppet-catalog_rspec'

And add this line to the top of your spec_helper.rb

require 'puppet-catalog_rspec'

STEP FOUR - generate some RSpec code (code here)

Now we are all set to generate some RSpec code for our Puppet defined type. Let’s start with the case where all default values are used. Comment out the line that contains:

it { is_expected.to compile }

and add this line below it:

it { dump_catalog }

Now run the tests again.

$ pdk test unit
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format progress
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}
it { is_expected.to contain_class('Settings')
}

it { is_expected.to contain_pizza_profile__kid_pizza('namevar')
  .with('age'    => '12')
  .with('dough'  => 'white')
  .with('cheese' => 'mozzarella')
}

it { is_expected.to contain_crust('namevar/medium_wholesome_thin_crust')
  .with('ensure' => 'baked')
  .with('size'   => '30')
  .with('type'   => 'thin')
  .with('dough'  => 'wholesome')
  .that_comes_before('Tomato_sauce[namevar/thick_cristal]')
}

it { is_expected.to contain_tomato_sauce('namevar/thick_cristal')
  .with('ensure'    => 'present')
  .with('type'      => 'cristal')
  .with('composure' => 'thick')
  .with('amount'    => '6')
  .that_comes_before('Cheese[namevar/a_lot_of_mozzarella]')
}

it { is_expected.to contain_cheese('namevar/a_lot_of_mozzarella')
  .with('ensure' => 'present')
  .with('type'   => 'mozzarella')
  .with('amount' => '5')
}

.

Finished in 0.42994 seconds (files took 2.79 seconds to load)
1 example, 0 failures

As you can see, some RSpec code is outputed. It is the total Puppet catalog but formatted like RSpec code. If we now copy this code from the output and put it in the kid_pizza_spec.rb in the place of the ` it { dump_catalog }`. We have our first test case.

Let’s run it:

$ pdk test unit --verbose
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format documentation
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}

pizza_profile::kid_pizza
  on centos-7-x86_64
    is expected to compile into a catalogue without dependency cycles
    is expected to contain Class[Settings]
    is expected to contain Pizza_profile::Kid_pizza[namevar] with age => "12", dough => "white" and cheese => "mozzarella"
    is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
    is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
    is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present", type => "mozzarella" and amount => "5"

Finished in 0.49949 seconds (files took 2.8 seconds to load)
6 examples, 0 failures

And ta ta we have six tests that actually check the content of the catalog.

STEP FIVE - Add the test cases to the RSpec file (code here)

Now we have some conditional logic to test. Let’s first add the test cases to your RSpec file. We replace the bulk of the current code by this:

# frozen_string_literal: true

require 'spec_helper'

describe 'pizza_profile::kid_pizza' do
  let(:title) { 'namevar' }

  on_supported_os.each do |os, os_facts|
    context "on #{os}" do
      let(:facts) { os_facts }

      context 'kids younger then 2' do
        let(:params) do
          { :age => 1 }
        end
      end

      context 'kids between 2 and 5' do
        let(:params) do
          { :age => 3 }
        end
        it { is_expected.to compile }
      end

      context 'kids between 5 and 8' do
        let(:params) do
          { :age => 7 }
        end
        it { is_expected.to compile }
      end

      context 'kids older then 8 (default case)' do
        let(:params) do
          {}
        end
        it { is_expected.to compile }

        it { is_expected.to contain_class('Settings')
        }
        
        it { is_expected.to contain_pizza_profile__kid_pizza('namevar')
          .with('age'    => '12')
          .with('dough'  => 'white')
          .with('cheese' => 'mozzarella')
        }
        
        it { is_expected.to contain_crust('namevar/medium_wholesome_thin_crust')
          .with('ensure' => 'baked')
          .with('size'   => '30')
          .with('type'   => 'thin')
          .with('dough'  => 'wholesome')
          .that_comes_before('Tomato_sauce[namevar/thick_cristal]')
        }
        
        it { is_expected.to contain_tomato_sauce('namevar/thick_cristal')
          .with('ensure'    => 'present')
          .with('type'      => 'cristal')
          .with('composure' => 'thick')
          .with('amount'    => '6')
          .that_comes_before('Cheese[namevar/a_lot_of_mozzarella]')
        }
        
        it { is_expected.to contain_cheese('namevar/a_lot_of_mozzarella')
          .with('ensure' => 'present')
          .with('type'   => 'mozzarella')
          .with('amount' => '5')
        }
      end
    end
  end
end

As you can see, we add the parameters with a specific age to create test cases for each of the cases in the if elseif logical branches.

STEP SIX - Extract the differences per logical branch (code here)

Now test case by test case, we start to use it { dump_catalog } to get the catalog for that case. But this time, we don’t just copy the whole RSpec code, but we only copy the things that are unique for this test case. The RSpec code for the new test case looks like this now:

context 'kids between 2 and 5' do
let(:params) do
    { :age => 3 }
end

it { is_expected.to contain_crust('namevar/medium_wholesome_thin_crust')
    .with('size'   => '10')
}

it { is_expected.to contain_tomato_sauce('namevar/thick_cristal')
    .with('amount'    => '4')
}

it { is_expected.to contain_cheese('namevar/a_lot_of_mozzarella')
    .with('amount' => '4')
}
end

context 'kids between 5 and 8' do
let(:params) do
    { :age => 7 }
end
it { is_expected.to contain_crust('namevar/medium_wholesome_thin_crust')
    .with('size'   => '20')
}

it { is_expected.to contain_tomato_sauce('namevar/thick_cristal')
    .with('amount'    => '5')
}

it { is_expected.to contain_cheese('namevar/a_lot_of_mozzarella')
    .with('amount' => '5')
}
end

Let’s run it:

$ pdk test unit --verbose
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format documentation
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}

pizza_profile::kid_pizza
  on centos-7-x86_64
    kids between 2 and 5
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "10"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "4"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "4"
    kids between 5 and 8
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "20"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "5"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "5"
    kids older then 8 (default case)
      is expected to compile into a catalogue without dependency cycles
      is expected to contain Class[Settings]
      is expected to contain Pizza_profile::Kid_pizza[namevar] with age => "12", dough => "white" and cheese => "mozzarella"
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
      is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present", type => "mozzarella" and amount => "5"

Finished in 0.74241 seconds (files took 2.83 seconds to load)
12 examples, 0 failures

STEP SEVEN - Refacter the common parts (code here)

In the previous step, we only added tests for the parts that are different for a certain age. But we would still like to test that the rest of the catalog is ok too. We can extract that part to a RSpec shared_examples that looks like this:


RSpec.shared_examples 'a kid pizza' do
  it { is_expected.to compile }

  it { is_expected.to contain_class('Settings')
  }
  
  it { is_expected.to contain_pizza_profile__kid_pizza('namevar')
    .with('dough'  => 'white')
    .with('cheese' => 'mozzarella')
  }
  
  it { is_expected.to contain_crust('namevar/medium_wholesome_thin_crust')
    .with('ensure' => 'baked')
    .with('type'   => 'thin')
    .with('dough'  => 'wholesome')
    .that_comes_before('Tomato_sauce[namevar/thick_cristal]')
  }
  
  it { is_expected.to contain_tomato_sauce('namevar/thick_cristal')
    .with('ensure'    => 'present')
    .with('type'      => 'cristal')
    .with('composure' => 'thick')
    .that_comes_before('Cheese[namevar/a_lot_of_mozzarella]')
  }
  
  it { is_expected.to contain_cheese('namevar/a_lot_of_mozzarella')
    .with('ensure' => 'present')
    .with('type'   => 'mozzarella')
  }
end

All of the age-specific stuff if removed from this, and only tests that run for every age are in the code block.

Now we can call this block from every test case. We do that like this:

context 'kids older then 8 (default case)' do
  let(:params) do
      {}
  end

  it_behaves_like 'a kid pizza'
  
  it { is_expected.to contain_crust('namevar/medium_wholesome_thin_crust')
      .with('size'   => '30')
  }
  
  it { is_expected.to contain_tomato_sauce('namevar/thick_cristal')
      .with('amount'    => '6')
  }
  
  it { is_expected.to contain_cheese('namevar/a_lot_of_mozzarella')
      .with('amount' => '5')
  }
  end
end

Let’s run the tests again and look at the output:

$ pdk test unit --verbose
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format documentation
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}

pizza_profile::kid_pizza
  on centos-7-x86_64
    kids between 2 and 5
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "10"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "4"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "4"
      behaves like a kid pizza
        is expected to compile into a catalogue without dependency cycles
        is expected to contain Class[Settings]
        is expected to contain Pizza_profile::Kid_pizza[namevar] with dough => "white" and cheese => "mozzarella"
        is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
        is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
        is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present" and type => "mozzarella"
    kids between 5 and 8
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "20"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "5"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "5"
      behaves like a kid pizza
        is expected to compile into a catalogue without dependency cycles
        is expected to contain Class[Settings]
        is expected to contain Pizza_profile::Kid_pizza[namevar] with dough => "white" and cheese => "mozzarella"
        is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
        is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
        is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present" and type => "mozzarella"
    kids older then 8 (default case)
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "30"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "6"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "5"
      behaves like a kid pizza
        is expected to compile into a catalogue without dependency cycles
        is expected to contain Class[Settings]
        is expected to contain Pizza_profile::Kid_pizza[namevar] with dough => "white" and cheese => "mozzarella"
        is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
        is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
        is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present" and type => "mozzarella"

Finished in 0.96298 seconds (files took 3.34 seconds to load)
27 examples, 0 failures

STEP EIGHT - Catalog not compiling (code here)

If you watched closely, you noticed that we did not yet fill in the test case for when an age below three is specified. Because there is no catalog, catalog_rspec cannot help you. You must do this yourself. Here is our test code for this logical branch:

context 'kids younger then 2' do
  let(:params) do
      { :age => 1 }
  end
  it { is_expected.to compile.and_raise_error(/probably not wise to order a pizza for a kid of 1./) }
end

Let’s run the tests again:

$ pdk test unit --verbose
pdk (INFO): Using Ruby 2.6.6
pdk (INFO): Using Puppet 7.9.0
[✔] Preparing to run the unit tests.
/bin/ruby -I/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/lib:/Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-support-3.10.2/lib /Users/tuorial/.pdk/cache/ruby/2.6.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit\}/\*\*/\*_spec.rb --format documentation
No facts were found in the FacterDB for Facter v4.2.2 on {:operatingsystem=>"CentOS", :operatingsystemrelease=>"/^7/", :hardwaremodel=>"x86_64"}, using v3.14.1 instead
Run options: exclude {:bolt=>true}

pizza_profile::kid_pizza
  on centos-7-x86_64
    kids younger then 2
      is expected to fail to compile and raise an error matching /probably not wise to order a pizza for a kid of 1./
    kids between 2 and 5
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "10"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "4"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "4"
      behaves like a kid pizza
        is expected to compile into a catalogue without dependency cycles
        is expected to contain Class[Settings]
        is expected to contain Pizza_profile::Kid_pizza[namevar] with dough => "white" and cheese => "mozzarella"
        is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
        is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
        is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present" and type => "mozzarella"
    kids between 5 and 8
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "20"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "5"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "5"
      behaves like a kid pizza
        is expected to compile into a catalogue without dependency cycles
        is expected to contain Class[Settings]
        is expected to contain Pizza_profile::Kid_pizza[namevar] with dough => "white" and cheese => "mozzarella"
        is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
        is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
        is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present" and type => "mozzarella"
    kids older then 8 (default case)
      is expected to contain Crust[namevar/medium_wholesome_thin_crust] with size => "30"
      is expected to contain Tomato_sauce[namevar/thick_cristal] with amount => "6"
      is expected to contain Cheese[namevar/a_lot_of_mozzarella] with amount => "5"
      behaves like a kid pizza
        is expected to compile into a catalogue without dependency cycles
        is expected to contain Class[Settings]
        is expected to contain Pizza_profile::Kid_pizza[namevar] with dough => "white" and cheese => "mozzarella"
        is expected to contain Crust[namevar/medium_wholesome_thin_crust] that comes before Tomato_sauce[namevar/thick_cristal]
        is expected to contain Tomato_sauce[namevar/thick_cristal] that comes before Cheese[namevar/a_lot_of_mozzarella]
        is expected to contain Cheese[namevar/a_lot_of_mozzarella] with ensure => "present" and type => "mozzarella"

Finished in 1.06 seconds (files took 3.21 seconds to load)
28 examples, 0 failures

Is this it?

No, probably not. You could (should) make some tests to see if the $dough and $cheese variables are applied correctly. You should probably do some better tests on the edges of the age domains. and use better variable values in the test to distinguish between the values of the $amount_of_tomato_souce variable and the $amount_of_cheese variable. But the goal of the blog was not to make the best possible test, but to explain a litte bit how to get started with the unit tests and use the rspec_catalog.

Conclusion

I hope this blog post helped you to start or improve your writing of Puppet RSpec tests. But if you could use a hand, we are here to help. Making good Puppet code, including good unit tests, is our bread and butter at Enterprise Modules. But besides developing our own modules, we are also helping customers build the best possible Puppet code. Do you think you could need some assistance? Don’t hesitate to contact us for some consultancy.

About us

Enterprise modules is the leading developer of enterprise-ready puppet modules for Oracle databases and Oracle WebLogic. Our puppet modules help sysadmins and DBAs to automate the installation, configuration, and management of their databases and application server systems. These modules allow them to make managed, consistent, repeatable, and fast changes to their infrastructure and automatically enforce the consistency.