Search code examples
ruby-on-railsrubyrspecrspec-rails

How to avoid using the expect_any_instance_of hammer?


I've got a few tests like this:

it 'should invite user again' do
    admin_user = create(:invited_admin_user)
    expect_any_instance_of(AdminUser).to receive(:invite!).and_return(true)
    patch :reinvite, params: { id: admin_user.to_param }
end

I really want to write it like this:

it 'should invite user again' do
    admin_user = create(:invited_admin_user)
    expect(admin_user).to receive(:invite!).and_return(true)
    patch :reinvite, params: { id: admin_user.to_param }
end

But the test fails if I do that. Any idea why that would happen? I'm using factory_bot to create the AdminUser instance.

I've tried putting puts statements in the test and the invite method to confirm the ID.

def invite!(_param1 = AdminUser.new, _param2 = {})
    puts 'ID in invite!' + self.id.inspect
    super(_param1, _param2)
end

it 'should invite user again' do
    admin_user = create(:invited_admin_user)
    puts 'adminuser created' + admin_user.id.inspect
    expect(admin_user).to receive(:invite!).and_return(true)
    patch :reinvite, params: { id: admin_user.to_param }
end

Result

adminuser created7768

ID in invite!7768


Solution

  • Why do you want to use mocks here in the first place?

    it 'should invite user again' do
      admin_user = create(:invited_admin_user)
      patch :reinvite, params: { id: admin_user.to_param }
      expect(admin_user.reload.invited).to eq(true)
    end
    

    If you want to avoid redundant calls to the database, the whole test should be written a) without a real DB object creation (FactoryGirl#build,) b) without patch call (directly call the respective controller’s method,) and c) mocking everything to be called in between.

    NB I personally do not see any reason at all to have tests where everything is mocked: they hardly differ from the code itself. I mean, we could make a mistake in the test as well as in the code and checking that patch calls the respective controller’s method is silly: it’s already checked in Rails tests. I always try to test real things, when applicable (like the user was indeed changed, rather than some method was invoked.)