Monday, December 23, 2013

Puppet rspec testing and hiera

Rspec testing your puppet modules supports you in having stable and functional modules.
For a couple of weeks there is hiera integration in rspec-puppet.

Basic Spec setup

You will need a couple of files for puppet module rspec testing:

# Install via
# bundle install --path vendor/gems
source ""

gem "mocha", :require => false
gem 'puppet',       '>= 3.1.1'
gem 'puppet-lint'
gem 'facter',       '>= 1.6.10'
gem 'rspec-puppet', :git => ""
gem 'rake',         '>= 0.9.2'
gem 'puppetlabs_spec_helper', '0.3.0'
gem 'test-unit'
require 'rake'
require 'rake/tasklib'
require 'rspec/core/rake_task'
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint'

desc "Run the tests" do |t|
  t.rspec_opts = ['--color', '-f d']
  t.pattern = 'spec/*/*_spec.rb'

desc 'Run puppet-lint on the one manifests'
task :onelint do
  PuppetLint.configuration.with_filename = true

  linter =
  matched_files = FileList['spec/fixtures/modules/one/manifests/**/*.pp']

  matched_files.to_a.each do |puppet_file|
    linter.file = puppet_file

  fail if linter.errors? || (
    linter.warnings? && PuppetLint.configuration.fail_on_warnings

task :default => [:spec_prep, :test, :onelint, :spec_clean]
require 'test/unit'
require 'mocha/setup'
require 'puppetlabs_spec_helper/module_spec_helper'
require 'puppet/indirector/hiera'
require 'hiera'

fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))

# include common helpers
support_path = File.expand_path(File.join(File.dirname(__FILE__), '..',
Dir[support_path].each {|f| require f}

RSpec.configure do |c|
  c.config = '/doesnotexist'
  c.manifest_dir = File.join(fixture_path, 'manifests')
  c.mock_with :mocha


If your module require other modules (e.g. stdlib functions) you have to include them using fixtures repositories. Add your own module as a symlink:

        stdlib: ""
        concat: ""
        <your module name>: "#{source_dir}"
 This will create all required directory structures in spec/fixtures/modules.

Hiera Configuration

Add a hiera.yaml and a hiera data file to your spec/fixtures:

  - yaml
  - test

    :datadir: 'spec/fixtures/hiera'

myvariable: 'myvalue'

Hiera Integration

Only minor changes are required to integrate hiera lookups into your spec tests.
The following example is taken from my stdmodule on github (

require 'spec_helper'
hiera_file = 'spec/fixtures/hiera/hiera.yaml'   # <- required to find hiera configuration file

# test our main class (init.pp)
describe 'stdmodule', :type => :class  do     # <- set the class name

    # test without hiera lookup (using class defaults)
    context 'without hiera data' do
        it { should contain_class('stdmodule') }                        # <- check for classes included
        it { should contain_class('stdmodule').with_myparam('foo') }    # <- check for classes with parameters
        it { should contain_class('stdmodule::basic') }                 # <- check for subclasses
        it { should contain_class('stdmodule::package_file_service') }
        it { should contain_stdmodule__defines('foobar_define') }       # <- check for defines

    # test with explicit params given on class definition
    context 'with explicit data' do
        let(:params) {{                                                 # <- set explizit params
            :myparam => 'newvalue'
        it { should contain_class('stdmodule').with_myparam('newvalue') }

    # test with explicit hiera lookup
    context 'with explicit hiera lookup' do
        # set hiera_config
        let (:hiera_config) { hiera_file }

        hiera = => hiera_file)                        # <- use Hiera ruby class
        variable = hiera.lookup('stdmodule::myparam', nil, nil)         # <- do hiera lookup
        let (:params) {{
            :myparam => variable                                        # <- set parameter to hiera lookup
        it { should contain_class('stdmodule').with_myparam(variable) }

1 comment:

  1. With all respect given to your efforts, this is actually wrong...

    Compare what you have done under the context "with explicit data" to what you have done under the context "with explicit hiera lookup." They are actually one and the same.

    In both cases, you explicitly passed a parameter into the class under test. It does not matter that in the second case, the _test code_ got the value for that parameter from hiera. The method of providing the data _to_ the class under test is no different in the two contexts. This means you have tested the same thing twice.

    What does _not_ get covered in this test is the scenario where no parameters are explicitly passed to the class under test and puppet take it upon itself to try and resolve values for each parameter from hiera. _That_ is missing from here.

    All that being said, I am having a great deal of difficulty figuring out how to actually make the missing test scenario work as described. The documentation is spotty and I actually found this blog post of yours while trying to find the answer.