rodjek / rspec-puppet

RSpec tests for your Puppet manifests
http://rspec-puppet.com
MIT License
364 stars 203 forks source link

how to unit test class with parameter of type Array[Type[CatalogEntry]] #715

Open brettjacobson opened 6 years ago

brettjacobson commented 6 years ago

I want to parameterize the notify parameter on an exec (to restart a service), and the code works "for real". But I cannot figure out the syntax to unit test the class.

Example project here: https://github.com/brettjacobson/puppet-demo

The failures are related to supplying a dummy resource in the :params block, usually Error while evaluating a Resource Statement, Class[Testdemo::Test1]: parameter 'notifications' index 0 expects a Type[CatalogEntry] value, got String

rodjek commented 6 years ago

Thanks for submitting this @brettjacobson, the main issue here is with my documentation so I'll leave this issue open until the docs are fixed, but here's a breakdown of the changes needed to get the tests in your sample module passing.


diff --git a/.fixtures.yml b/.fixtures.yml
index 2296adb..1c4c8f3 100644
--- a/.fixtures.yml
+++ b/.fixtures.yml
@@ -3,4 +3,4 @@
 ---
 fixtures:
   forge_modules:
-#     stdlib: "puppetlabs/stdlib"
+      powershell: "puppetlabs/powershell"

Not related to the error you're reporting, but the test manifest depends on the powershell module being in place.


diff --git a/spec/classes/test1_spec.rb b/spec/classes/test1_spec.rb
index 76d7e71..e3e0d32 100644
--- a/spec/classes/test1_spec.rb
+++ b/spec/classes/test1_spec.rb
@@ -7,7 +7,7 @@ describe 'testdemo::test1' do
       let(:pre_condition) { "service {'test2svc':}"}
       let(:params) do
         {
-          notifications: ["Service[test2svc]",]
+          notifications: [ref('Service', 'test2svc')]
         }
       end

This is the main thing that the current documentation is lacking. When preparing to run the tests, rspec-puppet has to take the various inputs from your spec file and convert it into a Puppet manifest and unfortunately it's rather difficult to programatically determine the intent behind the input strings (e.g. should "Service[test2svc]" be rendered as a string or as a reference?). For that reason, we default to assuming that things that look like strings are strings and your test manifest ended looking like this:

service { 'test2svc': }
class { 'testdemo::test1': notifications => ["Service[test2svc]"] }

To work around this (and this is the bit that isn't properly documented), there is a ref method that you can use when specifying your params to force rspec-puppet to render the value as a resource reference. The ref method takes two parameters: the resource type and the resource title/name. So with the change in the diff above, your test manifest ends up looking like this:

service { 'test2svc': }
class { 'testdemo::test1': notifications => [Service[test2svc]] }

diff --git a/spec/classes/test1_spec.rb b/spec/classes/test1_spec.rb
index e3e0d32..8d2f08f 100644
--- a/spec/classes/test1_spec.rb
+++ b/spec/classes/test1_spec.rb
@@ -12,7 +12,7 @@ describe 'testdemo::test1' do
       end

       it { is_expected.to compile }
-      it { is_expected.to contain_exec('sample-exec').with_notify("Service[test2svc]") }
+      it { is_expected.to contain_exec('sample-exec').that_notifies("Service[test2svc]") }
     end
   end
 end

While it is possible to test relationships using the with_* methods, it has a couple of downsides in that you must specify the exact relationships (so in this case, as you specified the type of this value as an array of catalogue entries it would have to be with_notify(["Service[test2svc]"])) and that it does not fully resolve the dependency graph (which is not a problem in this simple example).

The better solution is to use the special relationship matching methods which take care of the afore mentioned downsides for you automatically.