kettlepot
kettlepot

Reputation: 11011

Stubbing a helper method from inside helper specs

I'm building a Rails application and formulating tests using RSpec.

I wrote tests for a method I'm creating called current_link_to. This method is supposed to check whether the current page corresponds to the path I pass it and add the current class to the generated link in case it does.

Here is the spec:

require "spec_helper"

describe ApplicationHelper do
  describe "#current_link_to" do
    let(:name)     { "Products" }
    let(:path)     { products_path }
    let(:rendered) { current_link_to(name, path) }

    context "when the given path is the current path" do
      before { visit(path) }

      it "should return a link with the current class" do
        # Uses the gem "rspec-html-matchers" (https://github.com/kucaahbe/rspec-html-matchers)
        expect(rendered).to have_tag("a", with: { href: path, class: "current" }) do
          with_text(name)
        end
      end
    end

    context "when the given path is not the current path" do
      before { visit(about_path) }

      it "should return a link without the current class" do
        expect(rendered).to have_tag("a", with: { href: path }, without: { class: "current" } ) do
          with_text(name)
        end
      end
    end
  end
end

I then tried implementing my method following the spec:

module ApplicationHelper
  def current_link_to(name, path, options={})
    options.merge!({ class: "#{options[:class]} current".strip }) if current_page?(path)

    link_to(name, path, options)
  end
end

However, the tests fail with the following error:

Failure/Error: let(:rendered) { current_link_to(name, path) }

RuntimeError: You cannot use helpers that need to determine the current page unless your view context provides a Request object in a #request method

Since I don't really need the current_page? helper method to perform checks on the request, I decided that it would make sense to stub it.

I tried the following methods, but none of them worked:

  1. helper.double(:current_page? => true)
    Seems to stub the helper.current_page? method, but it's not the same method that's being called by my function.
  2. allow(ActionView::Helpers::UrlHelper).to receive(:current_page?).and_return(true)
    The stub seems not to be effective at all

While writing this question I stumbled onto the solution. I managed to stub the current_page? method using this in a before block:

allow(self).to receive(:current_page?).and_return(true)

It worked, however this solution raised more questions than it really answered. I am now baffled over how this works, as it seems weird that self in a before block would respond to current_page? and that said method would in fact be exactly the same one my helper is calling.

Even after reading documentation and trying to figure out how this works by littering my code with puts calls, the following doubts still haunt me:

It would be great if someone could help me figure this out because this is blowing my mind up, and I'm scared of using code that I don't really understand.

Upvotes: 6

Views: 2398

Answers (1)

TerryS
TerryS

Reputation: 7579

With respect, you're testing a little too much functionality here. The trick is to test only the bits you need to test.

In this instance, you only need to test that the current class is added when it needs to be, and isn't when it doesn't need to be.

This code should do the trick for you:

require 'rails_helper'

# Specs in this file have access to a helper object that includes
# the ApplicationHelper. 
RSpec.describe ApplicationHelper, type: :helper do

  describe 'current_link_to' do
    let(:subject) { helper.current_link_to('some_name', 'some_path', options = {}) }

    context 'where the path is current' do
      before do
        allow(helper).to receive(:current_page?).and_return true
      end
      it 'should include the current class' do
        expect(subject).to match /current/
      end
    end

    context 'where the path is not current' do
      before do
        allow(helper).to receive(:current_page?).and_return false
      end
      it 'should not include the current class' do
        expect(subject).to_not match /current/
      end
    end
  end
end

I've been a little glib and only tested for the presence of 'current' in the returned string. You could test for something like 'class="current"' if you want to be more precise.

The other key is the comment at the top of the page, which Rails inserts into blank helper specs for you:

# Specs in this file have access to a helper object that includes
# the ApplicationHelper. 

That means that you can use 'helper' where in your comment above you were using 'self', which makes things a little clearer (imho)

Hope it helps!

Upvotes: 1

Related Questions