1

I want to test that some instance is not receiving a specific method call. Trying to

RSpec.describe 'something' do
  before { subject.stubs(foo: 'bar') }
  it { is_expected.to_not receive(:foo) }
end

gives me

Failure/Error: it { is_expected.to_not receive(:foo) }

NoMethodError:
  undefined method `receive' for #<RSpec::ExampleGroups::Something:0x0000000010391588>

and trying to

RSpec.describe 'something else' do
  before { subject.stubs(foo: 42) }
  it { subject.expects(:foo).never; subject.foo }
end

passes. What am I doing wrong?

I was directed to the current not-working solution by Rails rspec undefined method `receive_message' for #<RSpec::ExampleGroups:: and I am using

RSpec 3.7
  - rspec-core 3.7.1
  - rspec-expectations 3.7.0
  - rspec-mocks 3.7.0
  - rspec-support 3.7.1

Update 1: I am using webmock instead of rspec-mocks.

Update 2: I am using webmock and mocha.

Update 3: Thanks for all the explanations, @engineersmnky! Unfortunately, for my specific use case, I still need to stub the method. Why is this one still passing?

require 'rspec'
require 'mocha'

RSpec.configure do |config|
  config.mock_with :mocha
end

RSpec.describe 'something' do
  # passes
  it 'can hide from foo' do 
    subject.stubs(:foo)
    expects(:foo).never
    subject.foo
  end
end

The difference is that I tried to express that I expect the signal :foo never to be sent.

Vega
  • 27,856
  • 27
  • 95
  • 103
jonas-schulze
  • 238
  • 1
  • 13
  • we need more here because this works just fine `subject { double }; it { is_expected.not_to receive(:anything) }` – Anthony Apr 30 '18 at 17:51
  • @engineersmnky the subject is set to the strings of the `describe` blocks: `'something'` and `'something else'`, implicitly. Also, the stubbing isn't the problem / is working. I am bound to `mocker`, I think. – jonas-schulze Apr 30 '18 at 18:10
  • @Anthony your test is passing as it indeed isn't calling `subject.anything` or `subject.send :anything`. In the MWE my test is calling it directly. In my actual code, I have an explicit subject that may call the method internally, that I don't want it to call. But all of this shouldn't (must not) make a difference. – jonas-schulze Apr 30 '18 at 18:11
  • @jonas-schulze where in your first example are you calling `subject.foo`? – Anthony Apr 30 '18 at 18:19
  • @Anthony Nowhere, hence I would expect it to pass. – jonas-schulze Apr 30 '18 at 18:20
  • if you remove the before block does it pass? I can't find that stubbing syntax in the rspec docs (closest was this: https://relishapp.com/rspec/rspec-mocks/v/3-7/docs/old-syntax/stub) – Anthony Apr 30 '18 at 18:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/170106/discussion-between-anthony-and-jonas-schulze). – Anthony Apr 30 '18 at 18:23
  • No it doesn't pass. I get the same error. RE the stubbing: as said above, I am not using `rspec-mocks` but a different framework: `webmock`. – jonas-schulze Apr 30 '18 at 18:25
  • @engineersmnky `it { subject.expects(:foo).never; subject.foo; puts subject.class.name }` puts `String` for me. Adding a `subject { 'something' }` to the second example does not change anything for me. – jonas-schulze Apr 30 '18 at 18:43
  • Yes, sorry, I am using `mocha`. Are you able to reproduce my errors? – jonas-schulze Apr 30 '18 at 18:50

1 Answers1

4

Okay so there are 2 problems here. First is you are trying to use rspec expectations syntax with mocha as your mocking frame work. This causes the:

undefined method `receive' for #<RSpec::ExampleGroups::Something:0x0000000010391588>

Error because mocha does not know about receive. The syntax for mocha should be:

it { expects(:foo).never }  

Your next issue is the before block is stubbing foo so it never actually reaches the receiver. So your second case passes because subject never actually receives foo because the stubbing intercepts the call.

Example:

require 'rspec'
require 'mocha'

RSpec.configure do |config|
  config.mock_with :mocha
end

def stub_obj(obj,method_list)
  Class.new(SimpleDelegator) do 
      method_list.each do |k,v| 
          define_method(k) {v}
      end
  end.new(obj)
end

RSpec.describe 'something' do

  subject {mock('subject')}
  #passes 
  it { expects(:foo).never }
  # fails 
  it { subject.expects(:foo).never; subject.foo }
  # passes 
  it 'can hide from foo' do 
    subject.stubs(:foo)
    subject.expects(:foo).never
    subject.foo
  end

  context 'kind of like this' do
    # passes
    it 'hides with a stubbed method' do
      stubber = stub_obj(subject, foo: 'bar')
      subject.expects(:foo).never
      stubber.foo
    end
    # fails 
    it 'cannot hide from a non-stubbed method' do
      stubber = stub_obj(subject, foo: 'bar')
      subject.expects(:bar).never
      stubber.bar
    end
  end
end

Here is the implementation of Object#stubs if you are interested and while it does a bit more than my example (#stub_obj) through a Mocha::Mockery object the concept is the same.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52
  • Thanks for the answer! Unfortunately my problem still isn't solved. I updated my question (update 3). I would be pleased if you could take a look, again. – jonas-schulze Apr 30 '18 at 19:42
  • @jonas-schulze as I described the stub intercepts the call to the receiver so `subject` never actually receives `foo` (thus the test passes) if you must test that functionality you should do so in isolation away from the stubbed `foo` – engineersmnky Apr 30 '18 at 19:57
  • @jonas-schulze I have added an example that is as clean as I can possibly represent this "hiding" feature – engineersmnky Apr 30 '18 at 20:06