How to test a Controller Concern in Rails 4

You will find many advice telling you to use shared examples and run them in the scope of your included controllers.

I personally find it over-killing and prefer to perform unit testing in isolation, then use integration testing to confirm the behavior of my controllers.

Method 1: without routing or response testing

Create a fake controller and test its methods:

describe MyControllerConcern do
  before do
    class FakesController < ApplicationController
      include MyControllerConcern
    end
  end

  after do
    Object.send :remove_const, :FakesController 
  end

  let(:object) { FakesController.new }

  it 'my_method_to_test' do
    expect(object).to eq('expected result')
  end

end

Method 2: testing response

When your concern contains routing or you need to test for response, rendering etc... you need to run your test with an anonymous controller. This allow you to gain access to all controller-related rspec methods and helpers:

describe MyControllerConcern, type: :controller do
  controller(ApplicationController) do
    include MyControllerConcern

    def fake_action; redirect_to '/an_url'; end
  end

  before do
    routes.draw {
      get 'fake_action' => 'anonymous#fake_action'
    }
  end
    
  describe 'my_method_to_test' do
    before do
      get :fake_action 
    end

    it do
      expect(response).to redirect_to('/an_url') 
    end
  end
end

As you can see, we define the anonymous controller with controller(ApplicationController). If your test concerne another class than ApplicationController, you will need to adapt this.

Also for this to work properly you must configure the following in your spec_helper.rb file:

config.infer_base_class_for_anonymous_controllers = true

Note: keep testing that your concern is included

It is also important to test that your concern class is included in your target classes, one line suffice:

describe SomeTargetedController do
  it 'includes MyControllerConcern' do
    expect(SomeTargetedController.ancestors.include? MyControllerConcern).to be(true) 
  end
end

Simplifying on method 2 from the most voted answer.

I prefer the anonymous controller supported in rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller

You will do:

describe ApplicationController, type: :controller do
  controller do
    include MyControllerConcern

    def index; end
  end

  describe 'GET index' do
    it 'will work' do
      get :index
    end
  end
end

Note that you need to describe the ApplicationController and set the type in case this does not happen by default.