Module: RubyLLM::Contract::RSpec::Helpers
- Defined in:
- lib/ruby_llm/contract/rspec/helpers.rb
Instance Method Summary collapse
-
#stub_all_steps(response: nil, responses: nil, &block) ⇒ Object
Set a global test adapter for ALL steps.
-
#stub_step(step_class, response: nil, responses: nil, &block) ⇒ Object
Stub a step to return a canned response without API calls.
-
#stub_steps(stubs, &block) ⇒ Object
Stub multiple steps at once with different responses.
Instance Method Details
#stub_all_steps(response: nil, responses: nil, &block) ⇒ Object
Set a global test adapter for ALL steps.
stub_all_steps(response: { default: true })
Supports an optional block form — the previous adapter is restored after the block returns (even if it raises):
stub_all_steps(response: { default: true }) do
# all steps use test adapter
end
# original adapter restored
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/ruby_llm/contract/rspec/helpers.rb', line 101 def stub_all_steps(response: nil, responses: nil, &block) adapter = build_test_adapter(response: response, responses: responses) if block previous = RubyLLM::Contract.configuration.default_adapter begin RubyLLM::Contract.configuration.default_adapter = adapter yield ensure RubyLLM::Contract.configuration.default_adapter = previous end else RubyLLM::Contract.configure { |c| c.default_adapter = adapter } end end |
#stub_step(step_class, response: nil, responses: nil, &block) ⇒ Object
Stub a step to return a canned response without API calls.
stub_step(ClassifyTicket, response: { priority: "high" })
result = ClassifyTicket.run("test")
result.parsed_output # => {priority: "high"}
Only affects the specified step — other steps are not affected.
With a block, the stub is scoped — cleaned up after the block:
stub_step(ClassifyTicket, response: data) do
# only stubbed inside this block
end
# ClassifyTicket no longer stubbed
Without a block, the stub lives until the RSpec example ends.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/ruby_llm/contract/rspec/helpers.rb', line 24 def stub_step(step_class, response: nil, responses: nil, &block) adapter = build_test_adapter(response: response, responses: responses) if block # Block form: use thread-local overrides with save/restore for real scoping overrides = RubyLLM::Contract.step_adapter_overrides previous = overrides[step_class] overrides[step_class] = adapter begin yield ensure if previous overrides[step_class] = previous else overrides.delete(step_class) end end else # Non-block: use RSpec allow (auto-cleaned after example) allow(step_class).to receive(:run).and_wrap_original do |original, input, **kwargs| context = kwargs[:context] || {} unless context.key?(:adapter) || context.key?("adapter") context = context.merge(adapter: adapter) end original.call(input, context: context) end end end |
#stub_steps(stubs, &block) ⇒ Object
Stub multiple steps at once with different responses. Takes a hash of step_class => options. Requires a block.
stub_steps(
ClassifyTicket => { response: { priority: "high" } },
RouteToTeam => { response: { team: "billing" } }
) do
result = TicketPipeline.run("test")
end
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/ruby_llm/contract/rspec/helpers.rb', line 63 def stub_steps(stubs, &block) raise ArgumentError, "stub_steps requires a block" unless block overrides = RubyLLM::Contract.step_adapter_overrides previous = {} stubs.each do |step_class, opts| opts = opts.transform_keys(&:to_sym) adapter = build_test_adapter(**opts) previous[step_class] = overrides[step_class] overrides[step_class] = adapter end begin yield ensure stubs.each_key do |step_class| if previous[step_class] overrides[step_class] = previous[step_class] else overrides.delete(step_class) end end end end |