How to Test Code Shared by Controllers and Helpers in Rails

Engineering
February 5, 2021
Alex Morton
Software Engineer
How to Test Code Shared by Controllers and Helpers in Rails
Welcome to The Observatory, the community newsletter from Orbit.

I'm Rosie, and I'll be your guide for this mission. Each week I'll go down rabbit holes so you don't have to. I'm here to share tactics, trends and valuable resources I've observed in the world of community building.

💫  Subscribe to The Observatory

Recently, I took on the task of refactoring some repetitive code that occurred several times throughout our codebase into a neat method. In other words, I made the code a bit more DRY (“Don’t Repeat Yourself” - a well-known convention in writing good code).

Essentially, the method (feature_enabled?), checks to see if a new feature has been enabled within the Orbit app for a user or their workspace.

Using the Flipper gem for feature flags

In this case, we used a gem called Flipper to help us turn on feature flags for certain users before implementing big feature changes to all of our users at once.

Here’s how the code looked before the refactored method:

{% c-block language="ruby" %}

if Flipper[:my_feature].enabled?(current_user) ||
 Flipper[:my_feature].enabled?(@workspace)
   # do something cool
end

{% c-block-end %}

And here’s how the same code would look with the feature_enabled? method:

{% c-block language="ruby" %}

if feature_enabled?(:my_feature, current_user, @workspace)
   # do something cool
end

{% c-block-end %}

Moving the method out of Application Helper and into Application Controller

When I first defined feature_enabled?, I did so in the application_helperfile. Consequently, the corresponding unit tests were written in application_helper_spec.

This method, however, needs to be used in both the helpers and the controllers. As I was working my way through this refactor, I started to run into an issue where the helper method wasn’t being recognised anywhere it was called in any controller files.

As I tried to work through it, I had a couple of different ideas as to what was going on:

  1. It wouldn’t be possible to call the new method from the controllers, so I'd have to duplicate it or leave things as they were or
  2. I needed to move the feature_enabled? method into the Application Controller so that the controllers have access to it

Spoiler alert! It ended up being the second option. So, I ended up transferring some code into different files by moving:

  1. The helper function (feature_enabled?) out of the Application Helper and into the Application Controller
  2. The corresponding unit test out of the Application Helper spec and into the Application Controller spec

Here’s what the function looks like in application_controller:

{% c-block language="ruby" %}

# frozen_string_literal: true
class ApplicationController < ActionController::Base

helper_method :feature_enabled?

protected

 def feature_enabled?(feature, user, workspace)
   Flipper[feature].enabled?(user) || Flipper[feature].enabled?(workspace)
  end
end

{% c-block-end %}


And here’s the corresponding application_controller_spec:

{% c-block language="ruby" %}

RSpec.describe ApplicationController, type: :controller do
let(:controller) { described_class.new }

 describe '#feature_enabled?' do
    let(:user) { create(:user) }
    let(:workspace) { create(:workspace) }
    let(:another_user) { create(:user) }

    subject { controller.send(:feature_enabled?, :my_feature, user, workspace) }

    it 'returns true if feature is enabled for everybody' do
      Flipper.enable(:my_feature)
      expect(subject).to be true
    end

    describe do
      context 'when feature is not enabled' do
        it { expect(subject).to be false }

        context 'when feature is enabled for this user' do
          before { Flipper.enable_actor(:my_feature, user) }
          it { expect(subject).to be true }
        end
      end
    end
    it 'returns true if feature is enabled for everybody' do
      Flipper.enable(:my_feature)
      expect(subject).to be true
    end
  end

{% c-block-end %}

Testing the method’s presence in the helpers and views

I think it was here that things were starting to come together for me, but it didn’t seem that the tests were accurately testing our feature_enabled? method.

By this point, we’d written the specs that proved feature_enabled? did what it is supposed to. However, we still needed to prove that the method would be available in the helpers, and therefore the views.

(That peace of mind is important, as someone could easily remove the helper_method :feature_enabled? call in application_controller by mistake and consequently break any usage of it in the helpers and views.)

To that extent, we needed to write a test that proved this method would indeed be available in the helper and view files.

My first approach was to try and write a test in application_helper_spec to verify the presence of the method, but it turns out that the Application Controller helper_methods are only added to the helpers during a real request/response cycle.

The application_helper_spec doesn’t simulate that - instead, it just calls a plain old object, meaning that feature_enabled? wasn’t defined when I tried to call it there.

I also realized it is the controller's (not the helper's) job to make sure the method exists in the helpers. After all, the helper_method call is located in the controller.

So once I realized that, it hit me that the application_helper was the wrong place for this spec. No wonder it was so difficult to test there!

Using view_context to test our helper method in the controller

From there, I went looking for a way to test our feature_enabled? method in the controller. This was the StackOverflow solution:

You can call any helper methods from a controller using the view_context, e.g. View_context.my_helper_method

With this next step in the right direction, I added a test that would specifically test whether the view_context of the controller had the helper method defined. I used the respond_to RSpec matcher for this, which calls respond_to? on the object to see if the method is defined:

{% c-block language="ruby" %}

describe '#view_context' do
  subject { controller.view_context }
  it { expect(subject).to respond_to(:feature_enabled?) }
end

{% c-block-end %}

Because of how view_context works in Rails, we can be sure that any methods it defines will be available in our helpers and views.

Conclusion

In conclusion, that’s how I figured out how to test a helper method from the Application Controller in Rails.

I removed the method from the Application Helper and transferred it to the Application Controller, and was then able to successfully test the behavior of the method in the Application Controller spec.

I also added a test to prove the feature_enabled? method would be available to helpers and views.

Thanks so much for reading! Feel free to stay in touch with us on Twitter (@OrbitModel).

💫  Orbit is Hiring Engineers in US/EMEA

Orbit helps grow and measure thousands of communities like Kubernetes and CircleCI. We're a remote-first company with a product-driven, empathetic engineering team that enjoys the occasional space pun! Check out our careers page for open opportunities.

Related Articles