admin管理员组

文章数量:1122832

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?

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?

Share Improve this question edited Nov 22, 2024 at 19:22 Tim Diggins asked Nov 22, 2024 at 12:20 Tim DigginsTim Diggins 4,5063 gold badges31 silver badges50 bronze badges 3
  • @engineersmnky FYI this is testing what methname receives, not what it outputs. Have updated the question – Tim Diggins Commented Nov 22, 2024 at 19:19
  • This sounds like you should be normalizing your inputs instead of making your method deal with whatever garbage you toss at it. Passing string keys as keywords arguments defeats the whole point as you can only access them through the keywords hash. – max Commented Nov 23, 2024 at 12:25
  • If you have to accept that kind of input do it through a postitional argument instead so that your intent is clear. – max Commented Nov 23, 2024 at 12:33
Add a comment  | 

1 Answer 1

Reset to default 0

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".

本文标签: