Search code examples
unit-testingchef-infracookbookchefspec

stub node.attribute? in chefspec


I'm trying to create a spec test for the following recipe code:

    if node.attribute?(node['tested_cookbook']['some_attribute'])
       include_recipe('tested_cookbook::first')
    else
       include_recipe('tested_cookbook::second')

I have the following spec for this:

    require 'spec_helper'

    describe 'tested_cookbook::default' do

    let(:chef_run) { ChefSpec::SoloRunner.new(platform: 'windows', version: '2008R2') do |node|
    node.set['tested_cookbook']['some_attribute'] = "some_value"
    end.converge(described_recipe) }

      it 'includes recipe iis' do
         expect(chef_run).to include_recipe('tested_cookbook::first')
      end
    end

The problem is that this test will always fail. How do I properly mock the outcome of 'node.attribute?' ? Thank you.


Solution

  • I'm not sure you can override the node object in Chefspec without monkey patching, which I think is probably more trouble than it's worth. I really almost never even see node.attribute? used, so it may be somewhat of an anti-pattern. (Do you really care if it was set, vs. if it has a non-nil value or not?)

    I would just avoid using attribute? in the first place, e.g.

    Recipe:

    if node['tested_cookbook'] && node['tested_cookbook']['some_attribute'])
       include_recipe('tested_cookbook::first')
    else
       include_recipe('tested_cookbook::second')
    end
    

    Spec:

    require 'spec_helper'
    
    describe 'tested_cookbook::default' do
    
    let(:chef_run) { ChefSpec::SoloRunner.new(platform: 'windows', version: '2008R2') do |node|
    node.set['tested_cookbook']['some_attribute'] = "some_value"
    end.converge(described_recipe) }
    
      it 'includes recipe iis' do
         expect(chef_run).to include_recipe('tested_cookbook::first')
      end
    end
    

    It's common practice to give these attributes a default value, too, so it would be even more idiomatic to say:

    attributes/default.rb:

    default['tested_cookbook']['some_attribute'] = 'second'
    

    recipe:

    include_recipe "tested_cookbook::#{node['tested_cookbook']['some_attribute']}"
    

    And then in your spec, do the same check as before. You're using an attribute to run ::second, but allowing someone to override it to ::first. If you don't like the pattern of actually using the attribute value to include, you could make it a flag and keep your previous if-statement too.