Module: BoltSpec

Defined in:
lib/bolt_spec/run.rb,
lib/bolt_spec/plans.rb,
lib/bolt_spec/plans/action_stubs.rb,
lib/bolt_spec/plans/mock_executor.rb,
lib/bolt_spec/plans/action_stubs/task_stub.rb,
lib/bolt_spec/plans/action_stubs/script_stub.rb,
lib/bolt_spec/plans/action_stubs/upload_stub.rb,
lib/bolt_spec/plans/action_stubs/command_stub.rb

Overview

These helpers are intended to be used for plan unit testing without calling out to target nodes. It accomplishes this by replacing bolt’s executor with a mock executor. The mock executor allows calls to run_* functions to be stubbed out for testing. By default this executor will fail on any run_* call but stubs can be set up with allow_* and expect_* functions.

Stub matching

Stubs match invocations of run_* functions by default matching any call but with_targets and with_params helpers can further restrict the stub to match more exact invocations. It’s possible a call to run_* could match multiple stubs. In this case the mock executor will first check for stubs specifically matching the task being run after which it will use the last stub that matched

allow vs expect

Stubs have two general modes bases on whether the test is making assertions on whether function was called. Allow stubs allow the run_* invocation to be called any number of times while expect stubs will fail if no run_* invocation matches them. The be_called_times(n) stub method can be used to ensure an allow stub is not called more than n times or that an expect stub is called exactly n times.

Configuration

By default the plan helpers use the modulepath set up for rspec-puppet and
an otherwise empty bolt config and inventory. To create your own values for
these override the modulepath, config, or inventory methods.

TODO:
- Allow description based stub matching
- Better testing of plan errors
- Better error collection around call counts. Show what stubs exists and more than a single failure
- Allow stubbing with a block(at the double level? As a matched stub?)
- package code so that it can be used for testing modules outside of this repo
- set subject from describe and provide matchers similar to rspec puppets function tests

MAYBE TODO?:
- allow stubbing for subplans
- validate call expectations at the end of the example instead of in run_plan
- resultset matchers to help testing canary like plans?
- inventory matchers to help testing plans that change inventory

Stubs:

  • allow_command(cmd), expect_command(cmd): expect the exact command

  • allow_script(script), expect_script(script): expect the script as <module>/path/to/file

  • allow_task(task), expect_task(task): expect the named task

  • allow_upload(file), expect_upload(file): expect the identified source file

  • allow_apply_prep: allows ‘apply_prep` to be invoked in the plan but does not allow modifiers

  • allow_apply: allows ‘apply` to be invoked in the plan but does not allow modifiers

Stub modifiers:

  • be_called_times(n): if allowed, fail if the action is called more than ‘n’ times

    if expected, fail unless the action is called 'n' times
    
  • not_be_called: fail if the action is called

  • with_targets(targets): target or list of targets that you expect to be passed to the action

  • with_params(params): list of params and metaparams (or options) that you expect to be passed to the action.

    Corresponds to the action's last argument.
    
  • with_destination(dest): for upload_file, the expected destination path

  • always_return(value): return a Bolt::ResultSet of Bolt::Result objects with the specified value Hash

    command and script: only accept 'stdout' and 'stderr' keys
    upload: does not support this modifier
    
  • return_for_targets(targets_to_values): return a Bolt::ResultSet of Bolt::Result objects from the Hash mapping

    targets to their value Hashes
    command and script: only accept 'stdout' and 'stderr' keys
    upload: does not support this modifier
    
  • return(&block): invoke the block to construct a Bolt::ResultSet. The blocks parameters differ based on action

    command: `{ |targets:, command:, params:| ... }`
    script: `{ |targets:, script:, params:| ... }`
    task: `{ |targets:, task:, params:| ... }`
    upload: `{ |targets:, source:, destination:, params:| ... }`
    
  • error_with(err): return a failing Bolt::ResultSet, with Bolt::Result objects with the identified err hash

Example:

describe "my_plan" do
  it 'should return' do
    allow_task('my_task').always_return({'result_key' => 10})
    expect(run_plan('my_plan', { 'param1' => 10 })).to be
  end

  it 'should call task with param1' do
    expect_task('my_task').with_params('param1' => 10).always_return({'result_key' => 10})
    expect(run_plan('my_plan', { 'param1' => 10 })).to eq(10)
  end

  it 'should call task with param1 once' do
    expect_task('my_task').with_params('param1' => 10).always_return({'result_key' => 10}).be_called_times(1)
    expect(run_plan('my_plan', { 'param1' => 10 })).to eq(10)
  end

  it 'should not_call task with 100' do
    allow_task('my_task').always_return({'result_key' => 10})
    # Any call with param1 => 100 will match this since it's added second
    expect_task('my_task').with_params('param1' => 100).not_be_called
    expect(run_plan('my_plan', { 'param1' => 10 })).to eq(10)
  end

  it 'should be called on both node1 and node2' do
    expect_task('my_task').with_targets(['node1', 'node2']).always_return({'result_key' => 10})
    expect(run_plan('my_plan', { 'param1' => 10 })).to eq(10)
  end

  it 'should average results from targets' do
    expect_task('my_task').return_for_targets({
      'node1' => {'result_key' => 20},
      'node2' => {'result_key' => 6} })
    expect(run_plan('my_plan', { 'param1' => 10 })).to eq(13)
  end

  it 'should construct a custom return value' do
    expect_task('my_task').return do |targets:, task:, params:|
      Bolt::ResultSet.new(targets.map { |targ| Bolt::Result.new(targ, {'result_key' => 10'})})
    end
    expect(run_plan('my_plan', { 'param1' => 10 })).to eq(10)
  end
end

See spec/bolt_spec/plan_spec.rb for more examples.

Defined Under Namespace

Modules: Plans, Run