10

There are already questions similar to this, but they all override the return values to nil unless .and_return is called as well

PROBLEM

I am wondering if there is a way to just check if a method is called using expect_any_instance_of(Object).to receive(:somemethod) and it runs normally without overriding or affecting the return value of .somemethod.

  • rspec-3.4.0
  • rails 4.2

Consider the following:

# rspec
it 'gets associated user' do
  expect_any_instance_of(Post).to receive(:get_associated_user)
  Manager.run_processes
end

# manager.rb
class Manager
  def self.run_processes
    associated_user = Category.first.posts.first.get_associated_user
    associated_user.destroy!
  end
end

The spec above although will work because :get_associated_user is called in the run_processes, however it raises NoMethodError: undefined method 'destroy!' for NilClass precisely because I mocked the :get_associated_user for any instance of Post.

I could add a .and_return method like expect_any_instance_of(Post).to receive(:get_associated_user).and_return(User.first) so that it will work without raising that error, but that already is a mocked return value (which might affect the rest of the code below it), and not the correct expected value it should have returned at the time the method is called.

I can however specify .and_return(correct_user) where correct_user is the user that is going to be the same return value as if it has ran normally. However, this will need me to mock every return value in the sequence Category.first.posts.first.get_associated_user just so that it will work normally. The actual problem is a lot more complex than above, therefore stubbing is not really a possible solution in my case.

Community
  • 1
  • 1
Jay-Ar Polidario
  • 6,463
  • 14
  • 28

1 Answers1

21

You can use and_call_original on the fluent interface to "pass through" the received message to the original method.

https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method

expect_any_instance_of(Post).to receive(:get_associated_user).and_call_original

However the use of expect_any_instance_of might be telling you that you have a code smell and you should be testing the behavior - not the implementation.

# test what it does - not how it does it.
it 'destroys the associated user' do
  expect { Manager.run_processes }.to change(Category.first.posts.first.users, :count).by(-1)
end
max
  • 96,212
  • 14
  • 104
  • 165
  • 2
    I've been looking for this for months! :) How could I miss that. Thanks a lot! You are right that `expect_any_instance_of` is not always gonna be accurate and specific to the test because precisely of the word `any instance`. I just have a specific case where I want to test the "implementation" rather than the "behavior" because it is manipulating an image, and I cannot think of a way to correctly test that the image has been updated as expected. Therefore, I just tested that the method is called, and pray that it worked :) – Jay-Ar Polidario Feb 17 '16 at 13:16
  • Don't you need to actually call the method after the `expect`? Like `Post.new.get_associated_user()`. – Iulian Onofrei Mar 11 '18 at 19:10
  • @IulianOnofrei no, not when you call expect with a block. RSpec runs `Manager.run_processes` (the block) once it reaches the last matcher. – max Mar 12 '18 at 22:17