0

Given the minimal example

# resources/novowel.rb
resource_name :novowel
property :name, String, name_property: true, regex: /\A[^aeiou]\z/

I would like to write the unit tests in spec/unit/resources/novowel_spec.rb

  • Resource 'novowel' for name should accept 'k'
  • Resource 'novowel' for name should accept '&'
  • Resource 'novowel' for name should NOT accept 'a'
  • Resource 'novowel' for name should NOT accept 'mm'

to ensure that the name property still works correctly even if the regex is changed for some reason.

I browsed several top notch Chef cookbooks but could not find references for such testing.

How can it be done? Feel free to provide more complex examples with explicitly subclassing Chef::Resource if that helps achieve the task.

Update 1: Could it be that Chef does NOT FAIL when a property does not fit the regex? Clearly this should not work:

link '/none' do
  owner  'r<oo=t'
  to     '/usr'
end

but chef-apply (12.13.37) does not complain about r<oo=t not matching owner_valid_regex. It simply converges as if owner would not have been provided.

arney
  • 795
  • 7
  • 20

2 Answers2

2

You would use ChefSpec and RSpec. I've got examples in all of my cookbooks (ex. https://github.com/poise/poise-python/tree/master/test/spec/resources) but I also use a bunch of custom helpers on top of plain ChefSpec so it might not be super helpful. Doing in-line recipe code blocks in the specs makes it waaaay easier. I've started extracting my helpers out for external use in https://github.com/poise/poise-spec but it's not finished. The current helpers are in my Halite gem, see the readme there for more info.

coderanger
  • 52,400
  • 4
  • 52
  • 75
  • To answer your edit, property validation is only run when the property is accessed, not when it is set. There are _reasons_ for this – coderanger Sep 16 '16 at 08:40
  • What does "accessing a property" mean? Something like in [this question](http://stackoverflow.com/q/25043690/2216961)? – arney Sep 16 '16 at 11:02
  • `owner` and `to` are resource properties. In provider code we have things like `new_resource.owner` to get the value of that property and do something with it. The validation code happens then. Because ChefSpec stubs out providers, that can leave things such that a property never gets accessed and so invalid values are allowed. – coderanger Sep 16 '16 at 18:43
  • So I could test the regex via RSpec'ing the provider? – arney Sep 16 '16 at 22:56
  • Unfortunately there is no simple answer here. Setting up a test harness via ChefSpec for this is somewhat more involved than will fit on StackOverflow. – coderanger Sep 16 '16 at 23:21
  • Quite strange. In the Puppet world, testing input validation for a native type is a simple RSpec exercise... – arney Sep 17 '16 at 11:18
  • The actual test part is easy, it's doing the broader harness. If you make a normal recipe with a bunch of test cases you can run it as per normal with `SoloRunner#converge`. – coderanger Sep 17 '16 at 15:48
  • I think I tricked out how to do this. Would you mind reviewing my answer below? – arney Sep 18 '16 at 21:13
0

We wrap the DSL inside a little Ruby in order to know the name of the resource's Ruby class:

# libraries/no_vowel_resource.rb
require 'chef/resource'

class Chef
  class Resource
    class NoVowel < Chef::Resource
      resource_name :novowel
      property :letter, String, name_property: true, regex: /\A[^aeiou]\z/
      property :author, String, regex: /\A[^aeiou]+\z/
    end
  end
end

and now we can use RSpec with

# spec/unit/libraries/no_vowel_resource_spec.rb
require 'spec_helper'

require_relative '../../../libraries/no_vowel_resource.rb'

describe Chef::Resource::NoVowel do
  before(:each) do
    @resource = described_class.new('k')
  end

  describe "property 'letter'" do
    it "should accept the letter 'k'" do
      @resource.letter = 'k'
      expect(@resource.letter).to eq('k')
    end

    it "should accept the character '&'" do
      @resource.letter = '&'
      expect(@resource.letter).to eq('&')
    end

    it "should NOT accept the vowel 'a'" do
      expect { @resource.letter = 'a' }.to raise_error(Chef::Exceptions::ValidationFailed)
    end

    it "should NOT accept the word 'mm'" do
      expect { @resource.letter = 'mm' }.to raise_error(Chef::Exceptions::ValidationFailed)
    end
  end

  describe "property 'author'" do
    it "should accept a String without vowels" do
      @resource.author = 'cdrngr'
      expect(@resource.author).to eq('cdrngr')
    end

    it "should NOT accept a String with vowels" do
      expect { @resource.author = 'coderanger' }.to raise_error(Chef::Exceptions::ValidationFailed)
    end
  end
end
arney
  • 795
  • 7
  • 20
  • This only works specifically with the name property (which, incidentally, shouldn't be called `:name`). – coderanger Sep 19 '16 at 05:09
  • @coderanger I beg to differ. Sure you can spec any property like this. See extended example above. – arney Sep 19 '16 at 17:23
  • @coderanger Could you please explain why it might be problematic to have a name property called `name`? – arney Sep 19 '16 at 17:38
  • Ah, when defining a custom property `name`, then this definition from `Chef::Resource` gets overridden: `property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(", ") : v.to_s }, desired_state: false` – arney Sep 21 '16 at 16:22