unit-testingmockingchef-infrachefspec

Mocking library functions when unit testing chef custom resource


I have created a very simple custom resource in Chef, and inside that resource is some simple logic. The logic in question calls out to some custom helper methods.

I can build the resource, and execute it as part of a recipe - but if I would like to unit test the behaviour within the resource itself to ensure the flow is correct. As such, I want to be able to mock the behaviour of those helper functions so that I can direct the resource behaviour. Unfortunately, I can't get this to work.

My recipe looks like this:

my_resource 'executing' do
    action :execute
end

The resource looks like this:

action :execute do
  if my_helper?(node['should_be_true'])
    converge_by "Updating" do
      my_helper_method
    end
  end
end

action_class do
  include CustomResource::Helpers
end

The functions are simple:

module CustomResource
  module Helpers
    def my_helper?(should_be_true)
      should_be_true
    end

    def my_helper_method
      hidden_method
    end

    def hidden_method
      3
    end
  end
end

When I try to mock the behaviour of these in my ChefSpec tests, I get errors:

it 'executes' do
  allow(CustomResource::Helpers).to receive(:my_helper?).and_return(true)
  expect(CustomResource::Helpers).to receive(:my_helper_method)
  expect { chef_run }.to_not raise_error
end


Failure/Error: expect(CustomResource::Helpers).to receive(:my_helper_method)

   (CustomResource::Helpers).my_helper_method(*(any args))
       expected: 1 time with any arguments
       received: 0 times with any arguments

Any ideas what I'm doing wrong in my mocking?

Thanks in advance!


Solution

  • Managed to make this work by changing the mocking method... Those module functions are added into the action_class, and therefore at runtime they are methods on that specific instance of the resource's ActionClass. Not sure if my solution is right/ideal - but it does work:

    include CustomResource::Helpers
    
    <snip>
    
    it 'executes' do
      allow_any_instance_of(Chef::Resource::ActionClass).to receive(:my_helper?).and_return(true)
      expect_any_instance_of(Chef::Resource::ActionClass).to receive(:my_helper_method)
      expect { chef_run }.to_not raise_error
    end
    

    I did look into avoiding the 'any instance of' mock, but then I got into problems and gave up - clearly the ActionClass has a lot of behaviour that I don't want to have to worry about.