Search code examples
ruby-on-railsruby-on-rails-4rspec-railsrspec3

How to test that a class is called in a controller method with RSpec


I am testing my controller to ensure that a library class is called and that the functionality works as expected. NB: This might have been asked somewhere else but I need help with my specific problem. I would also love pointers on how best to test for this.

To better explain my problem I will provide context through code. I have a class in my /Lib folder that does an emission of events(don't mind if you don't understand what that means). The class looks something like this:

class ChangeEmitter < Emitter
  def initialize(user, role, ...)
    @role = role
    @user = user
    ...
  end

  def emit(type)
    case type
    when CREATE
      payload = "some payload"
    when UPDATE
      payload = "some payload"
    ...
    end

    send_event(payload, current_user, ...)
  end
end

Here is how I am using it in my controller:

class UsersController < ApplicationController

  def create
    @user = User.new(user_params[:user])
    if @user.save
      render :json => {:success => true, ...}
    else
      render :json => {:success => false, ...}
    end
    ChangeEmitter.new(@user, @user.role, ...).emit(ENUMS::CREATE)
  end
end

Sorry if some code doesn't make sense, I am trying to explain the problem without exposing too much code.

Here is what I have tried for my tests:

describe UsersController do
  before { set_up_authentication }

  describe 'POST #create' do
    it "calls the emitter" do
      user_params = FactoryGirl.attributes_for(:user)
      post :create, user: user_params

      expect(response.status).to eq(200)
      // Here is the test for the emitter
      expect(ChangeEmitter).to receive(:new)
    end
  end
end

I expect the ChangeEmitter class to receive new since it is called immediately the create action is executed.

Instead, here is the error I get:

(ChangeEmitter (class)).new(*(any args))
       expected: 1 time with any arguments
       received: 0 times with any arguments

What am I missing in the above code and why is the class not receiving new. Is there a better way to test the above functionality? Note that this is Rspec. Your help will be much appreciated. Thanks.


Solution

  • You need to put your expect(ChangeEmitter).to receive(:new) code above the post request. When you are expecting a class to receive a method your "expect" statement goes before the call to the controller. It is expecting something to happen in the future. So your test should look something like:

    it "calls the emitter" do    
      user_params = FactoryGirl.attributes_for(:user)
    
      expect(ChangeEmitter).to receive(:new)
    
      post :create, user: user_params
    
      expect(response.status).to eq(200)
    end
    

    EDIT

    After noticing that you chain the "emit" action after your call to "new" I realized I needed to update my answer for your specific use case. You need to return an object (I usually return a spy or a double) that emit can be called on. For more information on the difference between spies and doubles check out: https://www.ombulabs.com/blog/rspec/ruby/spy-vs-double-vs-instance-double.html

    Basically a spy will accept any method called on it and return itself whereas with a double you have to specify what methods it can accept and what is returned. For your case I think a spy works.

    So you want to do this like:

    it "calls the emitter" do    
    
      user_params = FactoryGirl.attributes_for(:user)
    
      emitter = spy(ChangeEmitter)
      expect(ChangeEmitter).to receive(:new).and_return(emitter)
    
      post :create, user: user_params
    
      expect(response.status).to eq(200)
    end