>
> I guess I was hoping that when the instance double verified that the
> message matches the method signature it would determine that I was trying
> to use keyword arguments instead of a hash.


It's worth noting that keyword arguments really just a hash (plus some
syntactic sugar). Anyhow, verifying doubles do look at keyword arguments
when verifying a message you expect/allow or when you send a message to a
verifying double.  Here's an example:

``` ruby
class HTML
  def parse(body:, headers: {})
  end
end

RSpec.describe "Verifying doubles" do
  it 'verifies the keyword arguments when allowing a message' do
    html = instance_double(HTML)

    # this works...
    allow(html).to receive(:parse).with(body: "abc", headers: {a: 1})

    # ...but this triggers an "Invalid keyword arguments provided: bad_key"
error.
    allow(html).to receive(:parse).with(body: "abc", headers: {a: 1},
bad_key: 1)
  end

  it 'verifies the keyword arguments when receiving a message' do
    html = instance_double(HTML, parse: nil)

    # this works...
    html.parse(body: "abc", headers: {a: 1})

    # ...but this triggers an "Invalid keyword arguments provided: bad_key"
error.
    html.parse(body: "abc", headers: {a: 1}, bad_key: 1)
  end
end
```

This works as you'd expect--when allowing a message with arguments that the
method signature supports, it works, but when you allow a message with
arguments that the method signature does not support, it raises an
appropriate error.  And it behaves similarly when you send it a message.

In your original example, you had this:

``` ruby
expect(html).to respond_to(:parse).with_keywords(:body, :headers)
```

This didn't work how you expect, because the `respond_to` matcher is only
able to work off of the method signature of the `html` object (in this
case, a test double), and RSpec's verifying doubles implement the method
signature checks as _runtime_ behavior, rather than actually defining the
exact same method signature on the test double.  Essentially, the methods
defined on test doubles are all defined to accept `*args`, and then it
applies logic to those arguments at runtime to do things like the method
signature verification.  There simply isn't a simple, performant way for
test doubles to define the method signatures in the same way as you do on
normal classes.

As I said before, using a `respond_to` matcher on a test double like this
is basically just testing the test double, not testing your class.  You
could do something like this:

``` ruby
expect(HTML.new).to respond_to(:parse).with_keywords(:body, :headers)
```

...but I personally wouldn't bother with anything like that.  Such a test
seems not to have much value, IMO, and is likely to be brittle.

Myron

On Mon, Sep 18, 2017 at 11:53 PM, Steven Webb <[email protected]>
wrote:

> To expand further.
>
> If I'd continued using position arguments and changed from:
>
> def parse(body)
>
> to
>
> def parse(body, headers)
>
> the instance double could calls to this were using the correct number of
> arguments.
>
> Alternatively if I was already using keyword arguments and added another
> one:
>
> def parse(body:)
>
> to
>
> def parse(body:, headers: {})
>
> it would also get picked up.
>
> It's only because passing keywords arguments to a method can be
> interpreted as either a single hash or multiple arguments that this isn't
> picked up.
>
> Steve.
>
> On Tuesday, 19 September 2017 14:33:22 UTC+8, Steven Webb wrote:
>>
>> Thank you.
>>
>> I guess I was hoping that when the instance double verified that the
>> message matches the method signature it would determine that I was trying
>> to use keyword arguments instead of a hash.
>>
>> Steve.
>>
>> On Tuesday, 19 September 2017 14:20:10 UTC+8, Myron Marston wrote:
>>>
>>> What goal do you have in mind for this test? From the examples you gave,
>>> it looks like you are only testing how RSpec’s verifying doubles work. For
>>> example, this expectation:
>>>
>>> expect(html).to respond_to(:parse).with_keywords(:body, :headers)
>>>
>>> …isn’t exercising your code at all, because you’ve declared html as a
>>> test double, so it’s just testing how doubles work. If you’re trying to
>>> test the HTML class, you should not use a double in its place. Test doubles
>>> are intended for when you want to control the environment in which you test
>>> something, by replacing some collaborators with fake versions. They’re not
>>> intended to ever replace the thing you are testing—once you do that, you’re
>>> no longer testing the thing.
>>>
>>> Myron
>>> ​
>>>
>>> On Mon, Sep 18, 2017 at 11:03 PM, Steven Webb <[email protected]>
>>> wrote:
>>>
>>>> I'm having trouble testing a method signature change from taking a
>>>> single argument (a hash) to using keyword arguments. I've created a
>>>> contrived example of HTML parsing to simplify things (I'm not actually
>>>> writing a html parser):
>>>>
>>>> class HTML
>>>>   def parse(body) # body is a hash
>>>>     ...
>>>>   end
>>>> end
>>>>
>>>> I want to update it so that it can take an optional headers argument.
>>>> It becomes:
>>>>
>>>> class HTML
>>>>   def parse(body: , headers: {}) # body and headers are both hashes
>>>>   end
>>>> end
>>>>
>>>> In a related unit test of a different class I'm using something like
>>>> this:
>>>>
>>>> RSpec.describe "calling the parser" do
>>>>   let(:html) { instance_double("HTML", parse: nil) }
>>>>   let(:body) { double("body") }
>>>>   let(:headers) { double("headers") }
>>>>
>>>>   before { html.parse(body: body, headers: headers) }
>>>>
>>>>   it "allows passing optional headers" do
>>>>     expect(html).to have_received(:parse).with(body: body, headers:
>>>> headers)
>>>>   end
>>>> end
>>>>
>>>> The problem I've got is that this test passes before updating the HTML
>>>> class. After updating the HTML class it correctly detects the keywords as
>>>> arguments and passes. Before it incorrectly determines the keywords are the
>>>> "body" hash and passes. Both are valid ruby, but the method signature has
>>>> changed (at least to me, possibly not to the VM). I tried:
>>>>
>>>> it "allows an optional headers argument" do
>>>>   expect(html).to respond_to(:parse).with_keywords(:body, :headers)
>>>> end
>>>>
>>>> but that fails (presumably the instance double is using method_missing).
>>>>
>>>>   1) calling the parser allows an optional headers argument
>>>>      Failure/Error: expect(html).to respond_to(:parse).with_keywords(:body,
>>>> :headers)
>>>>        expected #<InstanceDouble(HTML) (anonymous)> to respond to
>>>> :parse with keywords :body and :headers
>>>>      # ./spec/keyword_args_spec.rb:40:in `block (2 levels) in <top
>>>> (required)>'
>>>>
>>>> Can anyone explain how I should be testing this correctly?
>>>>
>>>> Thanks
>>>>
>>>> Steve.
>>>>
>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "rspec" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to [email protected].
>>>> To post to this group, send email to [email protected].
>>>> To view this discussion on the web visit https://groups.google.com/d/ms
>>>> gid/rspec/8dbe9153-45c0-44f7-a0be-e6fa6ceffd7e%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/rspec/8dbe9153-45c0-44f7-a0be-e6fa6ceffd7e%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>> For more options, visit https://groups.google.com/d/optout.
>>>>
>>>
>>> --
> You received this message because you are subscribed to the Google Groups
> "rspec" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit https://groups.google.com/d/
> msgid/rspec/e9ba5c1c-bbed-4b71-84d3-078d31fd7a5f%40googlegroups.com
> <https://groups.google.com/d/msgid/rspec/e9ba5c1c-bbed-4b71-84d3-078d31fd7a5f%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/rspec/CADUxQmt83iXSFO0NHTe0asRMxmgSv%2BXMBwaVoqu%3DOydOWXjS5A%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to