Unit testing with rspec-puppet — for beginners
Editor's note: We invite you to join us at Puppetize PDX this 9-10 October 2019.
So you need to implement rspec testing of your Puppet modules… where do you start? Why is rspec-puppet testing important? How is this going to help my code? Testing allows you the peace of mind of knowing that even minor changes in your Puppet code will not break compilation of the Puppet catalog.
I recently found myself in this same situation — starting with rspec-puppet testing — and this write-up documents the struggles, pitfalls and shortcuts I learned along the way. By no means am I an expert on rspec-puppet testing, and in fact, I still have a lot to learn. But I'd like to offer what I've learned as a beginner to help you if you, too, are just starting out with rspec-puppet testing.
How should I get started with rspec-puppet?
First off, the main question I had was where to start. You have a Puppet module you have inherited, or just written yourself, so how do you get this rspec-puppet thing going to be able to test your modules, as Puppet recommends?
Instead of jumping directly into testing your modules, let's start with the basics needed to begin your testing. You'll need several Ruby gems. You can use Ruby Version Manager (RVM) or your preferred Ruby environment manager to install them.
- gem install puppet
- gem install puppet-lint
- gem install puppet-syntax
- gem install puppetlabs_spec_helper
Ok, that’s done, what next? Now we need to learn some basics about the directory structure of your rspec-puppet testing. As we are not going into using version control tools like Git or SVN, we will assume you already know how to do that, or have a local copy of the module that you are working on. The typical directory structure of your module looks something like this:
module_name/ ├── examples ├── files ├── lib ├── manifests │ └── init.pp ├── spec └── templates
Your rspec-puppet test files reside inside your
module_name/spec directory, and should have a structure similar to this:
spec/ ├── classes ├── defines └── fixtures (don’t worry about creating this directory as the first time you run `rake spec`, it will create it for you)
Now there could be other directories also, but for all intents and purposes of this write-up, this is all we will cover. There are three other files that are required for all this to come together:
- <module_name>/.fixtures.yml - describes dependency modules
- <module_name>/Rakefile - definition of Rake tasks
- <module_name>/spec/spec_helper.rb - specifications for the rspec-puppet tasks
How do I start writing spec tests?
With these in place, you are now ready to start writing your spec tests. Remember the
defines directories inside the
module_name/spec directory? As you might expect, classes go inside
classes and defined types go inside
defines. Let’s get started with a simple class file to start testing, something like this:
In this simple class, we install the nginx package, ensure the presence of the index.html and make sure the service is running and enabled. We know we can do both a
puppet parser validate and a
puppet-lint to test the syntax and format, but what about unit testing? This is where rspec-puppet comes into play.
Our class file for testing will have a name similar to
init_spec.rb, which represents the name of the class (in this case,
init) and with the suffix
_spec.rb, letting us know that it is a Ruby file specifically for spec testing.
This is the most basic file to start with, requiring the
spec_helper that we referenced earlier, and we are going to describe the test we are going to run. Between the
do and the
end is where our unit test code goes. Before we get to our code, there are some parameters we can set up for the tests. They look like this:
This is the use of the
let(:params) section. Now with that info, we are ready to expand on our rspec test.
To test specific aspects of your code, like the
package resource, you would expand or replace the compile part with something like this:
To test the presence of the
index.html file in the catalog, the code would look something like this:
Let's break this down just a bit. The first line,
contain_file(‘/var/www/index.html’), is the title of the resource you are testing. The
.with() is a hash of the attributes of the resource to test. We can also continue expanding our test to include the service resource, and our finished code might look like this:
How do I run my test?
To run your test, change to your
module_name/spec directory, and execute the following command:
The command tests that specific file and tells you if your tests have passed or failed and where, so you can fix any issues that may come up.
Can I test more than one operating system?
You might want to ask: Wait, what if I need to test more than just CentOS? Ah, now you're talking. We can do that with contextual testing. Instead of testing just a single operating system, we can test others as well. We could use
context as in the example below, and write tests for each OS. But there's a better way using
Using multiple contexts:
rspec-puppet-facts gem, the above code could then be simplified:
How does a Puppet class rspec-puppet test help me?
Congratulations, you have now completed your first Puppet class rspec-puppet test! You may ask, how does this help me, though? When changes are made to your class files — even minor changes — having a baseline set of tests that you can run against them will help determine whether you are properly maintaining your Puppet code. Couple that with
puppet parser validate, and what you end up with is a solid base for producing working Puppet code that we know is tested and sound. Granted, there is a lot more to learn, and we covered just the basics with our example.
Now, a bit of extra credit, so to speak. What if your class has multiple packages to install, or multiple directories to create? Do you really have to create a test for each one? No, you don't! You can take an array and the
.each iterator, and using syntax similar to that above, with just a little modification, you can test the catalog for the presence of the Apache, MySQL, phpMyAdmin, curl and Wget packages:
Thanks to Carthik Sharma for asking me to take on learning rspec-puppet to write tests for our modules. Many thanks also to David Schmitt for helping me when I ran into problems, answering all of my beginner's questions, and showing me cool tricks like the
Joseph Oaks is a technical training engineer at Puppet.