Testing
ALPHA
- TODO: document testing patterns
Mocking Axn calls
Say you're writing unit specs for PrimaryAction that calls Subaction, and you want to mock out the Subaction call.
To generate a successful Action::Result:
- Base case:
Action::Result.ok
- [Optional] Custom message:
Action::Result.ok("It went awesome")
- [Optional] Custom exposures:
Action::Result.ok("It went awesome", some_var: 123)
To generate a failed Action::Result:
- Base case:
Action::Result.error
- [Optional] Custom message:
Action::Result.error("It went poorly")
- [Optional] Custom exposures:
Action::Result.error("It went poorly", some_var: 123)
- [Optional] Custom exception:
Action::Result.error(some_var: 123) { raise FooBarException.new("bad thing") }
Either way, using those to mock an actual call would look something like this in your rspec:
let(:subaction_response) { Action::Result.ok("custom message", foo: 1) }
before do
expect(Subaction).to receive(:call).and_return(subaction_response)
end
call!
The semantics of call-bang are a little different -- if Subaction is called via call!
, you'll need slightly different code to handle success vs failure:
Success
let(:subaction_response) { Action::Result.ok("custom message", foo: 1) }
before do
expect(Subaction).to receive(:call!).and_return(subaction_response)
end
Failure
Because call!
will raise, we need to use and_raise
rather than and_return
:
let(:subaction_exception) { SomeValidErrorClass.new("whatever you expect subclass to raise") }
before do
expect(Subaction).to receive(:call!).and_raise(subaction_exception)
end
NOTE: to mock subaction failing via explicit fail!
call, you'd use an Action::Failure
exception class.
Mocking Axn arguments
Be aware that in order to improve testing ergonomics, the type
validation will return true
for any RSpec::Mocks::
subclass as long as Action.config.env.test?
is true
.
This makes it much easier to test Axns, as you can pass in mocks without immediately failing the inbound validation.
subject(:result) { action.call!(sym:) }
let(:action) { build_action { expects :sym, type: Symbol } }
context "with a symbol" do
let(:sym) { :hello }
it { is_expected.to be_ok }
end
context "with an RSpec double" do
let(:sym) { double(to_s: "hello") }
it { is_expected.to be_ok }
end
RSpec configuration
Configuring rspec to treat files in spec/actions as service specs (very optional):
RSpec.configure do |config|
config.define_derived_metadata(file_path: "spec/actions") do |metadata|
metadata[:type] = :service
end
end