Search code examples
ruby-on-railsunit-testingrspeccancan

Enforce Single Rails Controller Action Invocation From An RSpec Example


Please bear with me while I give some background to my question:

I was recently integrating CanCan into our application and found that one of the controller rspec tests failed. Turns out this was a result of the test being poorly written and invoking an action on the controller twice.

  it 'only assigns users for the organisation as @users' do
    xhr :get, :users, { id: first_organisation.to_param}
    expect(assigns(:users).count).to eq(3)

    xhr :get, :users, { id: second_organisation.to_param}
    expect(assigns(:users).count).to eq(4)
  end

Note, the example is cut for brevity.

Now the reason this fails is because rspec is using the same controller instance for both action calls and CanCan only loads the organisation resource if it isn't already loaded.

I can accept the reasoning behind a) rspec using a single instance of the controller for the scope of the example and b) for CanCan to be only loading the resource if it doesn't exist.

The real issue here is that of course it's a bad idea to be invoking an action twice within the same example. Now the introduction of CanCan highlighted the error in this example, but I am now concerned there may be other controller tests which are also invoking actions twice or that such examples may be written in the future, which, rather long-windedly, leads me to my question:

Is it possible to enforce that a controller rspec example can only invoke a single controller action?


Solution

  • Ok, it does appear I have a solution:

    Create unity_helper.rb to go into spec/support

    module UnityExtension
      class UnityException < StandardError
      end
    
      def check_unity
        if @unity
          raise UnityException, message: 'Example is not a unit test!'
        end
    
        @unity = true
      end
    end
    
    RSpec.configure do |config|
      config.before(:all, type: :controller)  do
        ApplicationController.prepend UnityExtension
        ApplicationController.prepend_before_action :check_unity
      end
    end
    

    then in rails_helper.rb

    require 'support/unity_helper'
    

    And this has actually highlighted another rspec controller example which is invoking a controller twice.

    I am open to other solutions or improvements to mine.