Testing Helpers That Use Scoped Translations

Update: While this technique is useful when you actually need to bind a template to your helper test, most of the time you can simply stub the helper right there, using something like stub(self).t('.title') { "Title" }

Once on a beautiful day the sun was shining and the room was full of pleasant warmth. I was whistling a tune happily to myself while writing the following rails helper.

  def title(page_title, show_title = true)
    content_for(:title) { t('.title', :default => page_title) }
    @show_title = show_title
  end

It’s so neat and simple. It allows you to write title "English Title" and provides a way to override title with translation scoped like pages.index.title.

Then came the test (apologies, TDD purists). Whistling along with the chirping birds I put the following in my helper test.

  require 'test_helper'

  class LayoutHelperTest < ActionView::TestCase
    context "#title helper" do
      should "create content for title" do
        title("foobar")
        assert_equal "foobar", @content_for_title
      end
    end
  end

Suddenly the birds went silent. The room got colder and darker. I got an error.

  NameError: undefined local variable or method `template' for #<LayoutHelperTest:0x102e8ab40>

Here’s what happened. Using scoped translations such as t('.title') requires a template. It makes perfect sense because scope can only be looked up for a particular template’s path. After some research I found that it’s quite easy to stub a template for tests to work. You can put something like this in your setup method.

  # Using RR (http://github.com/btakita/rr) for stubbing.
  # Easy to convert to equivalent in mocha/flexmock/etc.
  stub(self).template { ActionView::Template.new "app/views/index.html.haml" }

This is the kind of usefulness that could easily be made into a helper. You can put this in your test_helper.rb.

  def use_template(path)
    stub(self).template { ActionView::Template.new File.join("app", "views", path) }
  end

Now my test looks very neat.

  require 'test_helper'

  class LayoutHelperTest < ActionView::TestCase
    context "#title helper" do
      setup do
        use_template "pages/index.html.haml"
      end

      should "create content for title" do
        title("foobar")
        assert_equal "foobar", @content_for_title
      end
    end
  end

Running tests… Green! And birds are back!

Off Github

Right about now everyone is switching away from GitHub gem host. It’s crappy enough that you have to edit environments in your config files — you also have to reinstall the big pile of gems on your machines. Cleaning up my big old gem list doesn’t strike me as a fun leisure time.

Off Github is there to make this process less painful. Run it on the machines where you have gems installed and it will help you get rid of github legacy.

Drier gem configs

Update: This article is obsolete. Gemcutter should take first place in your gem sources. This way there is no reason to use :source option at all.

Now is the worst time for duplication in gem configs with all the changes going on. GitHub is not a gem host anymore. Soon RubyForge may follow the same path.

with_options provides a perfect way to minimize this duplication. I personally forgot it even existed until now. Watch how this mess:

  config.gem "settingslogic", :source => "http://gemcutter.org"
  config.gem "authlogic", :source => "http://gemcutter.org"
  config.gem "compass", :source => "http://gemcutter.org"
  config.gem "will_paginate", :source => "http://gemcutter.org"
  config.gem "inherited_resources", :source => "http://gemcutter.org"
  config.gem "haml", :source => "http://gemcutter.org"

becomes dry

  config.with_options :source => "http://gemcutter.org" do |c|
    c.gem "settingslogic"
    c.gem "authlogic"
    c.gem "compass"
    c.gem "will_paginate"
    c.gem "inherited_resources"
    c.gem "haml"
  end

Update: Forgot to mention that you need to require "active_support" in order for this to work in tests, etc. Thanks daeltar in comments.