RSpec Since 2.5.0
WARNING
This functionality was added in the 2.5.0 release and is currently experimental. Some matchers may be changed without maintaining compatibility. This documentation will attempt to detail the migration process should such a situation arise.
Installation
require "servactory/test_kit/rspec/helpers"
require "servactory/test_kit/rspec/matchers"RSpec.configure do |config|
config.include Servactory::TestKit::Rspec::Helpers
config.include Servactory::TestKit::Rspec::Matchers
# ...
endExample
Structure
.call!orcall:subject;validations:inputs;internals;outputs;
when required data for work is valid:be_success_service;have_output.
when required data for work is invalid:be_failure_service.
File
RSpec.describe UsersService::Create, type: :service do
describe ".call!" do
subject(:perform) { described_class.call!(**attributes) }
let(:attributes) do
{
first_name:,
middle_name:,
last_name:
}
end
let(:first_name) { "John" }
let(:middle_name) { "Fitzgerald" }
let(:last_name) { "Kennedy" }
describe "validations" do
describe "inputs" do
it do
expect { perform }.to(
have_input(:first_name)
.valid_with(attributes)
.type(String)
.required
)
end
it do
expect { perform }.to(
have_input(:middle_name)
.valid_with(attributes)
.type(String)
.optional
)
end
it do
expect { perform }.to(
have_input(:last_name)
.valid_with(attributes)
.type(String)
.required
)
end
end
describe "outputs" do
it do
expect(perform).to(
have_output(:full_name)
.instance_of(String)
)
end
end
end
context "when required data for work is valid" do
it { expect(perform).to be_success_service }
it do
expect(perform).to(
have_output(:full_name)
.contains("John Fitzgerald Kennedy")
)
end
describe "even if `middle_name` is not specified" do
let(:middle_name) { nil }
it do
expect(perform).to(
have_output(:full_name)
.contains("John Kennedy")
)
end
end
end
end
endclass UsersService::Create < ApplicationService::Base
input :first_name, type: String
input :middle_name, type: String, required: false
input :last_name, type: String
output :full_name, type: String
make :assign_full_name
private
def assign_full_name
outputs.full_name = [
inputs.first_name,
inputs.middle_name,
inputs.last_name
].compact.join(" ")
end
endHelpers
Helper allow_service_as_success!
Intended for mocking a call via call! with a successful result.
before do
allow_service_as_success!(UsersService::Accept)
endbefore do
allow_service_as_success!(UsersService::Accept) do
{
user: user
}
end
endHelper allow_service_as_success
Intended for mocking a call via call with a successful result.
before do
allow_service_as_success(UsersService::Accept)
endbefore do
allow_service_as_success(UsersService::Accept) do
{
user: user
}
end
endHelper allow_service_as_failure!
Intended for mocking a call via call! with a failed result.
before do
allow_service_as_failure!(UsersService::Accept) do
ApplicationService::Exceptions::Failure.new(
message: "Some error"
)
end
endHelper allow_service_as_failure
Intended for mocking a call via call with a failed result.
before do
allow_service_as_failure(UsersService::Accept) do
ApplicationService::Exceptions::Failure.new(
message: "Some error"
)
end
endOptions
Option with
The methods allow_service_as_success!, allow_service_as_success, allow_service_as_failure!, and allow_service_as_failure support the with option.
By default, this option does not require passing service arguments and will automatically determine this data based on the info method.
before do
allow_service_as_success!(
UsersService::Accept,
with: { user: user }
)
endbefore do
allow_service_as_success!(
UsersService::Accept,
with: { user: user }
) do
{
user: user
}
end
endMatchers
Matcher have_service_input
Alias: have_input
type
Checks the input type. Intended for one meaning.
it do
expect { perform }.to(
have_input(:id)
.type(Integer)
)
endtypes
Checks input types. Intended for multiple values.
it do
expect { perform }.to(
have_input(:ids)
.types(Integer, String)
)
endrequired
Checks whether the input is required.
it do
expect { perform }.to(
have_input(:id)
.type(Integer)
.required
)
endoptional
Checks whether the input is optional.
it do
expect { perform }.to(
have_input(:middle_name)
.type(String)
.optional
)
enddefault
Checks the default value of the input.
it do
expect { perform }.to(
have_input(:middle_name)
.type(String)
.optional
.default("<unknown>")
)
endconsists_of
Checks the nested types of the input collection. You can specify multiple values.
it do
expect { perform }.to(
have_input(:ids)
.type(Array)
.required
.consists_of(String)
)
endit do
expect { perform }.to(
have_input(:ids)
.type(Array)
.required
.consists_of(String)
.message("Input `ids` must be a collection of `String`")
)
endinclusion
Checks the values of the inclusion option of the input.
it do
expect { perform }.to(
have_input(:event_name)
.type(String)
.required
.inclusion(%w[created rejected approved])
)
endit do
expect { perform }.to(
have_input(:event_name)
.type(String)
.required
.inclusion(%w[created rejected approved])
.message(be_a(Proc))
)
endschema input (^2.12.0) internal (^2.12.0) output (^2.12.0)
Checks the values of the schema option of the input.
it do
expect { perform }.to(
have_input(:payload)
.type(Hash)
.required
.schema(
{
request_id: { type: String, required: true },
user: {
# ...
}
}
)
)
endit do
expect { perform }.to(
have_input(:payload)
.type(Hash)
.required
.schema(
{
request_id: { type: String, required: true },
user: {
# ...
}
}
)
.message("Problem with the value in the schema")
)
endmessage input (^2.12.0) internal (^2.12.0) output (^2.12.0)
Checks message from the last chain. Currently only works with consists_of, inclusion and schema chains.
it do
expect { perform }.to(
have_input(:ids)
.type(Array)
.required
.consists_of(String)
.message("Input `ids` must be a collection of `String`")
)
endmust
Checks for the presence of the expected key in the must input. You can specify multiple values.
it do
expect { perform }.to(
have_input(:invoice_numbers)
.type(Array)
.consists_of(String)
.required
.must(:be_6_characters)
)
endvalid_with
This chain will try to check the actual behavior of the input based on the data passed.
subject(:perform) { described_class.call!(**attributes) }
let(:attributes) do
{
first_name: first_name,
middle_name: middle_name,
last_name: last_name
}
end
it do
expect { perform }.to(
have_input(:first_name)
.valid_with(attributes)
.type(String)
.required
)
endMatcher have_service_internal
Alias: have_internal
type
Checks the type of an internal attribute. Intended for one meaning.
it do
expect { perform }.to(
have_internal(:id)
.type(Integer)
)
endtypes
Checks the types of an internal attribute. Intended for multiple values.
it do
expect { perform }.to(
have_internal(:ids)
.types(Integer, String)
)
endconsists_of
Checks the nested types of an internal attribute collection. You can specify multiple values.
it do
expect { perform }.to(
have_internal(:ids)
.type(Array)
.consists_of(String)
)
endit do
expect { perform }.to(
have_internal(:ids)
.type(Array)
.consists_of(String)
.message("Input `ids` must be a collection of `String`")
)
endinclusion
Checks the values of the inclusion option of an internal attribute.
it do
expect { perform }.to(
have_internal(:event_name)
.type(String)
.inclusion(%w[created rejected approved])
)
endit do
expect { perform }.to(
have_internal(:event_name)
.type(String)
.inclusion(%w[created rejected approved])
.message(be_a(Proc))
)
endschema input (^2.12.0) internal (^2.12.0) output (^2.12.0)
Checks the values of the schema option of an internal attribute.
it do
expect { perform }.to(
have_input(:payload)
.type(Hash)
.schema(
{
request_id: { type: String, required: true },
user: {
# ...
}
}
)
)
endit do
expect { perform }.to(
have_input(:payload)
.type(Hash)
.schema(
{
request_id: { type: String, required: true },
user: {
# ...
}
}
)
.message("Problem with the value in the schema")
)
endmessage input (^2.12.0) internal (^2.12.0) output (^2.12.0)
Checks message from the last chain. Currently only works with consists_of, inclusion and schema chains.
it do
expect { perform }.to(
have_input(:ids)
.type(Array)
.consists_of(String)
.message("Input `ids` must be a collection of `String`")
)
endmust
Checks for the presence of the expected key in the must internal attribute. You can specify multiple values.
it do
expect { perform }.to(
have_internal(:invoice_numbers)
.type(Array)
.consists_of(String)
.must(:be_6_characters)
)
endMatcher have_service_output
Alias: have_output
instance_of
Checks the type of the output attribute.
it do
expect(perform).to(
have_output(:event)
.instance_of(Event)
)
endcontains
INFO
In release 2.9.0 the with chain was renamed to contains.
Checks the value of the output attribute.
it do
expect(perform).to(
have_output(:full_name)
.contains("John Fitzgerald Kennedy")
)
endnested
Points to the nested value of the output attribute.
it do
expect(perform).to(
have_output(:event)
.nested(:id)
.contains("14fe213e-1b0a-4a68-bca9-ce082db0f2c6")
)
endMatcher be_success_service
it { expect(perform).to be_success_service }with_output
it do
expect(result.child_result).to(
be_success_service
.with_output(:id, "...")
)
endwith_outputs
it do
expect(result.child_result).to(
be_success_service
.with_outputs(
id: "...",
full_name: "...",
# ...
)
)
endMatcher be_failure_service
it { expect(perform).to be_failure_service }it "returns expected failure" do
expect(perform).to(
be_failure_service
.with(ApplicationService::Exceptions::Failure)
.type(:base)
.message("Some error")
.meta(nil)
)
endwith
it "returns expected failure" do
expect(perform).to(
be_failure_service
.with(ApplicationService::Exceptions::Failure)
)
endtype
it "returns expected failure" do
expect(perform).to(
be_failure_service
.type(:base)
)
endmessage
it "returns expected failure" do
expect(perform).to(
be_failure_service
.message("Some error")
)
endmeta
it "returns expected failure" do
expect(perform).to(
be_failure_service
.meta(nil)
)
end