hairydave
hairydave

Reputation: 11

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!

Upvotes: 0

Views: 1142

Answers (2)

hairydave
hairydave

Reputation: 11

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.

Upvotes: 1

coderanger
coderanger

Reputation: 54267

This is unfortunately very difficult given how ChefSpec works. You need to stub it on any instance of your resource, because that's the actual receiver. If you can use a module method instead, that is slightly easier. I take it you're already require_relative-ing your library file or the code would fail differently. However there is no way to do that for a custom resource because they are DSL-y and Ruby can't load them directly. The simplest option is generally to not test this kind of thing. Put code in the helper methods that checks if a node attribute is set and use that value if it is (specifics depend on the exact case), and then set that in your test. It's gross and annoying though, which is part of why Halite exists.

Upvotes: 0

Related Questions