How to test if a helper is calling another helper with a given value when not all values are known

1 view
Skip to first unread message

Jack Royal-Gordon

unread,
Aug 11, 2020, 8:56:14 PM8/11/20
to rs...@googlegroups.com
At it’s simplest, the question is as follows:

If I want test a method that calls another method with two values, and I want to check the value of the second argument but don’t know the value of the first argument, how can I do that? In essence,
we have m1 -> calls m2(a, b), I want to test the value of b but I don’t know the value of a. If I knew both values, I could just use “expect(object).to_receive(:m2).with(a, b)” But what do I use if I don’t know what “a” should be (I know what class it should be, but #with is not using === to compare values, so that doesn’t help.

———  More detail that explains why I don’t know the value of “a" ———

I’m writing specs for some helpers. For one controller, I have a set of helpers like follows:

form_editing_for_type(type, &block) ultimately generates a “form_for … fields_for …” with the block inside the #fields_for

nav_buttons(f, options) generates Preview, Next, Cancel buttons for the FormBuilder object “f”; “options” has certain overrides for defaults; it must be called from the block passed to form_editing_for_type

prev_buttom(f, options) these three helpers are called by nav_buttons only
next_button(f, options)
cancel_button(f, options)

nav_button(f, name, default_label, disabled, options)
this helper is called by the above three helpers only

So far, I’m testing each helper separately (including the ones only called by other helpers. The problem I’m encountering is that I want to test that the “options” passed into “nav_buttons” is being passed on to “prev_button”, etc. I could do that with something like the following:
it ‘passes “options” through” do
  @options = ...
  expect(self).to receive(:prev_button).with(ActionView::Helpers::FormBuilder, @options).and_call_original
  form_editing_for_type(@type) { |f| 
            nav_buttons(f, @options)
          }
end

But, there are a few issues here.

1) Is “self” the correct object to expect #prev_button to be called against?
2) I don’t know the exact value of the first argument to :prev_button, so how do I tell expect that I don’t really know the value (I do know @options and I that’s what I’m checking)? ActionView::Helpers::FormBuilder does not work (apparently #with is not using “===“ to check the value).

And then, the bigger issue: does it maybe make more sense, since the last four helpers are essentially private, to just test #nav_buttons for all the things that I would have tested the other four helpers for? This might better fit the idea of testing the desired result, not how the result is obtained.

Phil Pirozhkov

unread,
Aug 12, 2020, 7:32:29 PM8/12/20
to Jack Royal-Gordon
Hi Jack,

I suggest you use the `anything` argument matcher https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/setting-constraints/matching-arguments for the first argument.
To my best knowledge, `with` works quite fine with argument matchers, so the following passes:
```
class A
  def x
    y(1, 2)
  end

  def y(a, b)
  end
end

RSpec.describe 'A' do
  specify do
    a = A.new
    expect(a).to receive(:y).with(anything, be_even)
    a.x
  end
end
```


No, I don't think `self` would work. You probably meant `subject` or a named subject. You have to implicitly or explicitly define the subject, and make expectations on it. https://relishapp.com/rspec/rspec-core/v/3-9/docs/subject



I would personally recommend testing just one of the following, not both:
 - side effect (a method called with certain arguments) - in this case, `and_call_original` should be unnecessary
 - the return value of the whole call

- Phil


--
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 rspec+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/5A25DDD3-BCDF-4015-BBE0-1217A24E5735%40pobox.com.

Jack Royal-Gordon

unread,
Aug 12, 2020, 7:59:03 PM8/12/20
to rs...@googlegroups.com
Thanks, Phil. I didn’t know about the anything matcher — that’s exactly what I’m looking for. 

And, BTW, “self” did work. I’m mocking a helper called by another helper in the same file; I figured since I can call the helper being tested directly as a method of self, then I must be able to refer to the secondary helper as a method of self also. There might be a better/more elegant way to do it, but I couldn’t find it. I did try using the name of the Helper module, and it did resolve successfully but then the test indicated that it was called zero times instead of one, so I apparently got a different method/class. I can’t imagine what I would assign to subject to make usable.

Reply all
Reply to author
Forward
0 new messages