How to Stub Controller Methods in RSpec Request Specs! (FTW)

// Jun 18, 2015

Recently I’ve had to restructure our company’s API, and part of that was writing new request specs for everything.

One HUGE headache I ran into while writing request specs for the API was bypassing controller before_actions such as require_user, as well as stubbing out crucial controller instance methods like current_user.

I tried searching online; I don’t exaggerate when I say that I tried nearly everything:

let(:user) { FactoryGirl.create(:user) }

controller.stub(:current_user) { user }
#=> deprecated syntax

allow(controller).to receive(:current_user) { user }
#=> couldn't recognize current_user

allow(controller).to receive(:current_user).and_return(user) 
#=> still couldn't recognize current_user...

allow(ApplicationController)to receive(:current_user).and_return(user)
#=> by this point I was ready to give up...

You get the idea… Hours of searching Stack Overflow and Relish and different docs didn’t yield me the answer I needed.

Then I realized that current_user, require_login and all these methods were INSTANCE METHODS. Not class methods. Here’s the correct way to stub out instance methods in RSpec:

let(:user) { FactoryGirl.create(:user) }

it "tests a controller action that requires a current user" do
  allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
  get "/some/controller/action/path"

  expect(response.code).to eq("200")
  # ...
end

This RSpec helper allows you to stub out any instance method that exists on any class. However, my major use for this has been for stubbing out controller methods such as current_user, any controller before actions and access tokens if I’m doing request specs on a Rails API.

Example of controller action to test:

# Here we have users#show which will 
# render the occupation of the user in json

class Api::UsersController < Api::ApplicationController
  doorkeeper_for :all
  before_action: require_user

  def show
    occupation = current_user.occupation
    render json: { occupation: occupation }, status: 200
  end

end


In this example, we are using the Doorkeeper gem to protect the controller against unauthorized API calls (the following example will work with other token authorization methods too, the process is similar).

So in this situation we would have to stub out the access token and the current user in order to successfully test the response of our request:

describe "GET /users/show " do
  let(:user) { FactoryGirl.create(:user) }
  let(:token) { double acceptable?: true } # this is way to stub doorkeeper tokens, if you're using other gems or methods, please refer to their respective counterparts.

  before(:each) do
    allow_any_instance_of(API::ApplicationController).to receive(:doorkeeper_token).and_return(token)
    allow_any_instance_of(API::ApplicationController).to receive(:current_user).and_return(user)
    get "/users/show"
  end

  it "should respond with status 200" do
    expect(response.code).to eq("200")
  end
end

#=> 1 example, 1 success

We have now succesfully stubbed out current_user and doorkeeper_token and can now successfully test our controller#action!

BUT WAIT. There’s a bunch of code used just for stubbing, it would suck if we had to repeat that code over and over again for every request spec that we create… So let’s move that into a spec support in order to write less code!

# /app/spec/support/api_controller_helper.rb

module ApiControllerHelper
  def stub_access_token(token)
    allow_any_instance_of(Api::ApplicationController).to receive(:doorkeeper_token).and_return(token)
  end

  def stub_current_user(user)
    allow_any_instance_of(Api::ApplicationController).to receive(:current_user).and_return(user)
  end
end

RSpec.configure |config| do
  config.include ApiControllerHelper
end


In the end, our spec will look like this:

describe "GET /users/show " do
  let(:user) { FactoryGirl.create(:user) }
  let(:token) { double acceptable?: true }

  before(:each) do
    stub_access_token(token)
    stub_current_user(user)
    get "/users/show"
  end

  it "should respond with status 200" do
    expect(response.code).to eq("200")
  end
end

#=> 1 example, 1 success


It took me quite a while to figure this out but in the end it was more than worth it. Armed with this knowledge, I’m sure you’ll be able to bang out pages and pages of request specs in no time!

TL;DR Use allow_any_instance_of(Object).to receive(method_on_object).and_return(mock_method_used_for_testing) to mock an instance method on any object. Such as current_user on a controller.


Fuck yeah.