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.