Testing a Rails controller concern (using RSpec)

Minh Reigen
2 min readOct 18, 2023
Photo by Maarten Deckers on Unsplash

Testing a Rails controller concern can be approached in various ways. Here are a few methods that come to mind, listed from my least preferred to most preferred:

Including the concern test coverage inside a controller spec file

The concept here involves invoking a method from your concern within your controller spec file. However, this approach can make controller tests complex and less desirable. Additionally, if the concern’s method is reused across multiple controllers, you face the question of whether to duplicate similar tests for each controller. If not, this may warrant testing the concern method in isolation as a standalone unit test. This concern has led to the adoption of an alternative method commonly employed: the use of a “dummy” or “fake” controller.

Using a fake controller

The advantage of this approach is that you can maintain your own my_concern_spec.rb file, containing all your tests for this specific concern.

The drawbacks of this approach include: untidy code, the inability to utilize controller-specific testing methods and helpers, which can complicate testing tasks such as rendering and response verification.

# in spec/controllers/concerns/my_concern_spec.rb
describe MyConcern do
before do
class FakeController < ApplicationController
include MyConcern
end
end

# This ensures that FakeController is only present and defined for the duration of the test and is removed afterward.
after do
Object.send :remove_const, :FakeController
end

let(:object) { FakeController.new }

it "my method" do
expect(object).to eq("expected result")
end
end

Fret not, we can fix those drawbacks by using the “anonymous” controller method below!

Using an anonymous controller

# in spec/controllers/concerns/my_concern_spec.rb

describe ApplicationController, type: :controller do
# anonymous controller!
controller do
include MyConcern

def fake_action
your_concern_action_to_test
end
end

before { routes.draw { get "/fake_action" => "anonymous#fake_action" } }

describe "#your_concern_action_to_test" do
it "passes" do
get :fake_action, params: {my_id: 123}
expect(response).to be("abc")
end
end
end

The sole minor inconvenience with this method is the necessity to define routes for any fake actions you create within your anonymous controller. However, the advantage it offers is the ability to harness the provided controller-specific testing methods and helpers, just like you would in any other controller tests.

Final thoughts

Placing tests specific to a concern within its dedicated spec file delivers several advantages. It promotes the segregation of responsibilities, allowing you to focus solely on testing the specific functionalities of the concern’s methods. This organizational approach enhances the clarity and maintainability of your tests, making your codebase more orderly. As a result, the time required to create new tests for additional concerns and their methods decreases, as you already have an established framework in place.

Lately, I’ve found myself pondering teamwork and leadership, and it’s hard to ignore that this approach along with other efficiency oriented methods also offers benefits for any team since it contributes to enhanced efficiency, time savings, and resource conservation in the long run.

--

--