Nick Shears
Nick Shears

Reputation: 1133

How to access a controller helper method from a helper test in Rails 4?

I've got a method defined in ApplicationController as a helper method.

helper_method :can_access_participant_contact_data?

I'm trying to write a test for a helper method that resides in a helper file. This helper method makes a call to helper_method :can_access_participant_contact_data?

# In participants_helper.rb
#
def redacted_contact_data participant, attribute_name
  attribute = participant.try(:contact_data).try(attribute_name)
  return attribute if can_access_participant_contact_data?(participant)
  return nil       if attribute.blank?
  return attribute.gsub(/\S/i, '*') # Asterisked string
end

All I'm doing so far in my test is making a call to redacted_contact_data

require 'test_helper'

class ParticipantsHelperTest < ActionView::TestCase

   test "should return an asterisked string with spaces" do
     redacted_contact_data(Participant.first, :name)
   end

end

When I run my test, I'm getting this message

undefined method `can_access_participant_contact_data?' for #<ParticipantsHelperTest:0x007fd6c7c6d608>

I've been having a look around but I'm not sure how to get around this issue. Do I need to mock can_access_participant_contact_data? somehow? or can I just include the method into the test?

Upvotes: 5

Views: 3115

Answers (2)

Fumisky Wells
Fumisky Wells

Reputation: 1199

Another idea is to do "helper test" in "controller test" as follows:

require 'test_helper'

class ParticipantsControllerTest < ActionController::TestCase
  setup do
    # do some initialization here. e.g. login, etc.
  end

  test "should return an asterisked string with spaces" do
    participant = ...
    get :show, id: participant.id
    assert_equal '...',  @controller.view_context.redacted_contact_data(...)
  end
end

Where, @controller is ParticipantsController object already defined by rails controller testing framework (or you can explicitly define it when controller name is different from *ControllerTest), and view_context is the object for helper methods (see https://api.rubyonrails.org/classes/ActionView/Rendering.html#method-i-view_context for more detail).

Helper method often refer controller object and/or method (like session, request) so that it is sometimes difficult to do unit-test only in test/helpers/*. This is the reason why I test helper in controller in such a case.

Upvotes: 0

Jay-Ar Polidario
Jay-Ar Polidario

Reputation: 6603

AFAIK (As far as I know), you cannot fix this without stubbing, or doing some change in your code, as essentially a helper file is just a module of itself that should be treated independent of where it's gonna be included. Who knows you might want to include such helper file inside your model files for example, in which incidentally the model file also has a method named can_access_participant_contact_data? but does differently from that one defined in the ApplicationController, therefore you cannot unit test this without specifying the context / base.

Possible Workarounds:

  • Stubbing:

    • Use Mocha or rework testing into RSpec
    • Or manually (maybe there's a better way) by:

      test "should return an asterisked string with spaces" do
        ParticipantsHelper.class_eval do
          define_method :can_access_participant_contact_data? do |arg|
            true
          end
        end
      
        redacted_contact_data(Participant.first, :name)
      end
      
  • Or, moving all your ApplicationController helper methods into a separate/existing helper file, say inside your already existing ApplicationHelper. Then afterwards, include that helper inside your other helper file that you are testing that is making use of the method/s. i.e.:

    # helpers/application_helper.rb
    module ApplicationHelper
      def can_access_participant_contact_data?(participant)
        # YOUR CODE
      end
    end
    
    # helpers/participants_helper.rb
    module ParticipantHelper
      include ApplicationHelper
    
      def redacted_contact_data participant, attribute_name
        attribute = participant.try(:contact_data).try(attribute_name)
        return attribute if can_access_participant_contact_data?(participant)
        return nil       if attribute.blank?
        return attribute.gsub(/\S/i, '*') # Asterisked string
      end
    end
    
    • If using this approach, then two ways to call the helper method inside the controller:

      • Use Rails helpers method inside a controller:

        class ParticipantsController
          def show
            helpers.can_access_participant_contact_data?(@participant)
          end
        end
        
      • Or, include the helpers directly (I personally prefer the other approach just above)

        class ApplicationController < ActionController::Base
          include ApplicationHelper
        end
        
        class ParticipantsController < ApplicationController
          def show
            can_access_participant_contact_data?(@participant)
          end
        end
        
    • For the view files, you won't need to update any code.

Upvotes: 3

Related Questions