5

I created a Chef resource which 'extends' the deploy resource of chef. The basic idea is to check for the existence of a file deploy/crontab similar to mechanisms deploy/after_restart.rb in the source to be deployed, and create cronjobs out of it.

While this mechanism works as it should be (see https://github.com/fh/easybib-cookbooks/blob/t/cron-tests/easybib/providers/deploy.rb#L11-L14), I am struggling with a ChefSpec based test for it. I am currently trying to create mocks using FakeFS - but when I mock the Filesystem before the Chef run, the run fails because no cookbooks are found, since they do not exist in the mocked filesystem. If I dont, the mocked file deploy/crontab is obviously not found, so the provider doesnt do anything. My current approach is to trigger FakeFS.activate! directly before runner.converge(described_recipe) in the chef_run.

I would love to hear some recommendations how to do a proper test here: Is there maybe some possiblity to enable the FakeFS only directly before the deploy-resource-run, or to mock the Filesystem only partially?

2 Answers2

5

I had a similar problem stubbing the File system classes. The way that I have been solving this problem is as follows.

::File.stub(:exists?).with(anything).and_call_original
::File.stub(:exists?).with('/tmp/deploy/crontab').and_return true

open_file = double('file')
allow(open_file).to receive(:each_line).and_yield('line1').and_yield('line2')

::File.stub(:open).and_call_original
::File.stub(:open).with('/tmp/deploy/crontab').and_return open_file
punkle
  • 952
  • 1
  • 8
  • 17
  • Yes, this works- but my problem here is that I have to mock the content of the file as well, since I have to read/parse the content: https://github.com/fh/easybib-cookbooks/blob/t/cron-tests/easybib/providers/crontab.rb#L22 – Florian Holzhauer Mar 27 '14 at 09:17
  • Similarly you can do the following. ::File.stub(:open).with(anything).and_call_original ::File.stub(:open).with('/tmp/deploy/crontab').and_return 'content' – punkle Mar 27 '14 at 09:19
  • No, since this is a string, and does (according to the error) not supply the `each_line` method :) – Florian Holzhauer Mar 27 '14 at 09:51
  • You can mock the open file object that is returned by the File.open? – punkle Mar 27 '14 at 09:53
  • Sorry for the delay. It does mostly work ;) I uploaded a shrunk down version of my current test setup here: https://github.com/fh/chefspec-debugging The problem is that it apparently never reaches https://github.com/fh/chefspec-debugging/blob/master/easybib/providers/crontab.rb#L17 - the mocked return value of the each_line call seems to be wrong, since it does not execute the loop. Not sure why, maybe you have an idea. – Florian Holzhauer Mar 30 '14 at 09:36
  • After some more tweaking: it should not use `and_return`, but `and_yield`, with multiple yields if multiple lines are needed. I updated your response, the tests works now :) – Florian Holzhauer Mar 30 '14 at 10:46
  • Great, glad to hear it! – punkle Mar 30 '14 at 10:54
  • @punkle Thanks for your post, I think it's better in general to use `::File.stub(:open).and_call_original` instead of `::File.stub(:open).with(anything).and_call_original`. Indeed, it will catch any usages of `open`. The old way was failing for me in some cases because it was called sometimes with one argument and another time with two. – Matt Apr 20 '14 at 14:00
  • @Matt that makes total sense. I have updated the answer. Thanks! – punkle Apr 22 '14 at 12:06
5

Since punkle's solutions is syntactically deprecated and there are some parts missing, I'll try to give a new solution:

require 'spec_helper'

describe 'cookbook::recipe' do

  let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }


  file_content = <<-EOF
...here goes your
multiline
file content
EOF

  describe 'describe what your recipe should do' do

    before(:each) do
      allow(File).to receive(:exists?).with(anything).and_call_original
      allow(File).to receive(:exists?).with('/path/to/file/that/should/exist').and_return true
      allow(File).to receive(:read).with(anything).and_call_original
      allow(File).to receive(:read).with('/path/to/file/that/should/exist').and_return file_content
    end

    it 'describe what should happen with the file...' do
      expect(chef_run).to #...
    end
  end

end
Michael Lihs
  • 7,460
  • 17
  • 52
  • 85