Search code examples
rubyrspecchef-infrachefspec

Feedback on Ruby / ChefSpec coding style


I'm rather new to Ruby, but I have been doing a lot of research on Chef testing for the past two weeks. This test uses ChefSpec & Fauxhai, but it doesn't look very "ruby-ish" and I was hoping the community could give me some pointers on coding style. Is there a better way to write a nested loop like this?

cookbooks/foo/recipes/default.rb

package "foo" do
  action :install
end

cookbooks/foo/spec/default_spec.rb

require 'chefspec'

describe 'foo::default' do
  platforms = { 
    "debian"   => ['6.0.5'],
    "ubuntu"   => ['12.04', '10.04'],
    "centos"   => ['5.8', '6.0', '6.3'],
    "redhat"   => ['5.8', '6.3'],
    "mac_os_x" => ['10.6.8', '10.7.4', '10.8.2'],
    "windows"  => ['2008R2']
  }

  platforms.each do |platform,versions|
    versions.each do |version|
      context "on #{platform} #{version}" do
        before do
          Fauxhai.mock(platform: platform, version: version)
        end

        it 'should install foo' do
          @runner = ChefSpec::ChefRunner.new.converge('foo::default')
          @runner.should install_package 'foo'
        end
      end
    end
  end
end

Any and all feedback is welcome. Thank you!


Solution

  • First, a common practice is to extract ChefRunner instantiation to let helper. You can also include all Fauxhai configuration there:

    let(:chef_run) do
      ChefSpec::ChefRunner.new(platform: platform, version: version) do |node|
        node.set['foo']['bar'] = 'baz'
        # ....
      end.converge('foo::default')
    end
    
    it "installs foo" do
      expect(chef_run).to install_package 'foo'
    end
    

    The expect syntax seems to be recommended over should. But in this example I would use a one-liner:

    subject do
      ChefSpec::ChefRunner.new(platform: platform, version: version).converge('foo::default')
    end
    it { should install_package 'foo' }
    

    To clean up the looping a bit you can use RSpec's shared examples. A bit more extended example:

    require 'chefspec'
    
    shared_examples 'foo' do |platform, version|
      context "on #{platform} #{version}" do
        let(:users) { %w[user1 user2] }
        let(:chef_run) do
          ChefSpec::ChefRunner.new(platform: platform, version: version) do |node|
            node.set['foo']['users'] = users
          end.converge('foo::default')
        end
        subject { chef_run }
    
        it { should install_package 'foo' }
    
        it "creates specified users" do
          users.each { |u| expect(chef_run).to create_user u }
        end
      end
    end
    
    describe 'foo::default' do
      platforms = {
        'debian'   => ['6.0.5'],
        'ubuntu'   => ['12.04', '10.04'],
        'centos'   => ['5.8', '6.0', '6.3'],
        'redhat'   => ['5.8', '6.3'],
        'mac_os_x' => ['10.6.8', '10.7.4', '10.8.2'],
        'windows'  => ['2008R2']
      }
    
      platforms.each do |platform, versions|
        versions.each do |version|
          include_examples 'foo', platform, version
        end
      end
    end