What is the best way to rspec test a subclass in a parameterised puppet module

2.1k views Asked by At

We are using Puppet(v3.6.2) with the Foreman enc and have a lot of custom modules with the pattern demonstrated in the following illustration module;

#mkdir/manifests/init.pp
class mkdir ($path, $mode) {
  class {'mkdir::file': }
}

#mkdir/manifests/file.pp
class mkdir::file {
  file {$mkdir::path:
    ensure => 'directory',
    mode   => $mkdir::mode,
  }
}

#mkdir/spec/classes/mkdir_spec.rb
require 'spec_helper'
describe 'mkdir' do
  let(:params) {{ :path=>'/foo', :mode=>'777' }}
  it { should contain_class( 'mkdir::file' ) }
end

# All good so far, but here's the rub
# mkdir/spec/classes/file_spec.rb
require 'spec_helper'
describe 'mkdir::file' do
  # how can I set $mkdir::path and $mkdir::mode???
  let(:params) {{ :path=>'/bar', :mode=>'555' }}

  it { should contain_file('/bar').with({
    mode=>'555' })
  }
end

As the code comment says, how module parameter $mkdir::path and $mkdir::mode be set within the file_spec test?

Obviously the module could be refactored to pass all needed parameters to each subclass, and that's what I'll do if it's the only way, but it seems unlikely that there isn't a way to test this pattern as it stands.

I have also seen lots of examples using a module::params pattern, and this is certainly a clean pattern for installations using hiera, but I haven't been able to make that pattern work for Foreman, and at best it would require including two puppet classes per parameterised module which is ugly.

2

There are 2 answers

1
Felix Frank On BEST ANSWER

Your conclusions are about right. But the params class pattern is independent of your scheme of node level configuration. It might be possible to tie Hiera into it more tightly, but here is what you generally want to do:

  1. Define default values in the params class. Defaults might depend on Fact values such as $osfamily.
  2. Make the module's main class inherit the params class and use the variables declared in params as default values for the module class parameters.
  3. When using an ENC, make sure to pass node variable data to the module as parameter values, so as to overwrite the defaults from the params class where appropriate.
  4. Make the module class forward all the respectively necessary configuration data to each class in the module.

For example:

class mkdir::params {
    case $osfamily {
        'Debian': { $path = '/usr/share/foo' }
        'RedHat': { $path = '/var/lib/foo' }
        default:  { fail "The $osfamily platform is not supported by the mkdir module." }
    }
    $mode = 644
}

class mkdir($mode = $mkdir::params::mode,
            $path = $mkdir::params::path) inherits mkdir::params
{
    class { 'mkdir::file': mode => $mode, path => $path }
}

class mkdir::file($path, $mode) {
    # ...
}

Writing tests should be very straight forward then.

0
TaninDirect On

It may be a better solution to refactor and use Felix's solution, but for posterity another option is:

#spec_helper.rb
def setup_mkdir(p_mode, p_path)
  return "class { 'mkdir':
                    mode => p_mode
                    path => p_path}"
end

# mkdir/spec/classes/file_spec.rb
require 'spec_helper'
describe 'mkdir::file' do
  let(:pre_condition) do
    [ setup_mkdir('555', '/bar') ]
  end

  it { should contain_file('/bar').with({
    mode=>'555' })
  }
end

The key being to mock up the base class in a pre_condition block. Dont Repeat Yourself and put the class mock in a parameterised method.