Sometimes Puppet providers execute their logic based on the contents of a
@property_hash, which is just a representation of a resource’s actual state.
Its easy enough to test a provider for the basics, but populating the
@property_hash for a unit test has always been something of a chore, and
often gets skipped, leaving large portions of provider code untested. I wish
I’d understood this years ago, but now that I’ve got my head round it, its
pretty simple.
The @property_hash is populated during a call to the prefetch method, which
often receives a bunch of hashes representing resources that currently exist
from the instances method. This means that you often see the instances
method making the calls out to APIs, or executing commands to get the ‘full
list of what is’, so to speak. The prefetch method then just matches that
full list to what we’ve asked for in manifest.
Alright, so how to test it?
Basically what I want to simulate is a resource in its entirety. This include the resource as its parsed from the manifest, and also ensure that the provider handles detection of that resource’s actual state in the world. I recently ran into this on the AWS module while writing some tests, so I’ll use examples from that. The concept applies regardless though.
First we need to build a fake resource object that we can pass to prefetch()
so it knows what to look for. For this, I make a few objects, since they can
each be useful on their own.
let(:resource_hash) {
{
name: 'lb-1',
instances: ['web-1'],
listeners: [
{
'instance_port' => '80',
'instance_protocol' => 'TCP',
'load_balancer_port' => '80',
'protocol' => 'TCP'
}
],
availability_zones: ['sa-east-1a'],
region: 'sa-east-1',
}
}
let(:resource) {
Puppet::Type.type(:elb_loadbalancer).new(resource_hash)
}
This gives us a simple resource object that we can pass to prefetch() in a
unit test.
data = provider.class.prefetch({"lb-1" => resource})
What we’ve done here is pass the resource object as the value in a hash that has the key of the resource title.
Now the data object above is an array of live resources that we can inspect.
If the lb-1 resource was matched, we should find it in the array. The full
test looks like this.
it 'should correctly find existing load balancers' do
VCR.use_cassette('elb-named-test') do
data = provider.class.prefetch({"lb-1" => resource})
expect(data[0].exists?).to be_truthy
end
end
In the above example, the provider’s exists? method just returns
@property_hash[:ensure] == :present, so by getting a true value back, we know
that the @property hash has been populated from the fixture, and matched
against the resource we passed to the prefetch call. The VCR library is
just what we are using to capture the API calls made to AWS and store them as
fixtures. If fixtures were handled from other files, this would work just as
well.
Getting the @property_hash fully populated is especially important if the
provider does most (all) of the actual work in the flush method, for example,
where perhaps the only source of information about what needs adjusting is
entirely based on the contents of the @property_hash.
Anyway, this has helped me test some parts of my provider code that I would have previously just glossed over, and now I’ve got a bit more confidence in the code as I move it towards production.