Working with rspec-expectations 3.13.3 and have code such as:
def methname(**kwargs)
# let's assume this method doesn't mind whether the kwargs are keyed by symbols or strings
p(received: kwargs)
end
with a test such as
expect(something).to receive(:methname).with(hash_including(what_ever: "else"))
But in some cases the keyword arguments are being created as strings, not symbols, so that fails and I have to do
expect(something).to receive(:methname).with(hash_including("what_ever" => "else"))
I can get this right in each spec, but it would be cleaner for me if I could match it indifferently -
# desired code
expect(something).to receive(:methname).with(hash_including_indifferently(what_ever: "else"))
is there a way to already do this?
Is there a way to already do this?
There is no built in matcher that supports this but we can use built in matchers to accomplish this goal (for single use) or create our own matcher (multi-use).
Solution
If you need this frequently or need it to be more flexible in what is or is not included in the Hash
, your best bet is to define your own matcher. See: Custom Matchers.
For Example this will work:
RSpec::Matchers.define_negated_matcher :not_include, :include
RSpec::Matchers.define :indifferently_include do |expected|
match(:notify_expectation_failures => true) do |actual|
fit_pattern = expected.map do |k,v|
include(k.to_s).and(not_include(k.to_sym)).or(
include(k.to_sym).and(not_include(k.to_s))
).and(
include(k.to_s => v).or(include(k.to_sym => v))
)
end.reduce(&:and)
expect(actual).to fit_pattern
end
end
RSpec::Matchers.alias_matcher :hash_including_indifferently, :indifferently_include
Usage:
expect(something).to receive(:methname).with(hash_including_indifferently({"what_ever" => "else", another_key: "too"}))
Methodology
If you want to test in such a way as to allow kwargs
to contain either of {"what_ever" => "else"}
or {what_ever: "else"}
you can test as follows:
expect(something).to receive(:methname) do |h|
expect(h).to include(what_ever: "else").or include("what_ever" => "else")
end
because receive
with a block will yield the arguments to the block and you can test them explicitly using a Compound Expectation.
However if the Hash
contains both :what_ever
and "what_ever"
as keys as long as one of them has the value "else"
this test will pass. Given that your intent is for this test to be indifferent about whether the key is a String
or Symbol
, the keys would need to be uniquely indifferent as well, so you may want to test as:
RSpec::Matchers.define_negated_matcher :not_include, :include
expect(something).to receive(:methname) do |h|
expect(h).to include("what_ever").and(
not_include(:what_ever)
).or(
include(:what_ever).and(
not_include("what_ever")
)
).and(
include("what_ever" => "else").or(
include(what_ever: "else")
)
)
end
This will prevent ambiguity for any Hash
that might contain both "what_ever"
and :what_ever
keys, while ensuring that one of them is present with the value "else"
.