Pothibo

Testing helpers in Rails 3. Or how I wasted a day learning.

It happens to everyone, more often than we would like to admit. You start the day with 2 goals and you say to yourself: “Man, this is going to be a nice day, I’ll take my time, do it right™.” You sit down in front of the computer, ready to tackle everything that life computer will throw at you.

Yesterday was that kind of day for me. I struggle with test. I think they are useful, but it’s a fucking nightmare to get started. What should be tested? What shouldn’t? A good rule of thumb is to start with your models. If you follow the fat-model, skinny controller mindset, testing your models has the benefits of covering a lot of logic while being not too hard to get right.

But hey man, my helpers, presenters, decorators, whatever have some logic and render some html! How can I test that?

I was this guy, and boy didn’t I know what was coming my way yesterday. I’m not sure why, but testing the view logic is done via the controller. And testing controllers is like eating sand. Perhaps it’s changing with Rails 4, I don’t know.

Let me give you a break here and show you how you can test your helpers without having to climb the Kangchenjunga.

ActionView::TestCase

Before I start working my way to get the assertion working, I need to make sure that all the helpers I use in my function are available. ActionView::TestCase provides that for you.

class MyHelperModuleTest < ActionView::TestCase
  include MyHelperModule

  test "MyHelperShouldReturnSomeHTML" do
    my_helper_function()
  end
end

You can use some assertions that are designed to work with DOM elements. If you skim through the doc, you see that assert_select can assert on the dom using CSS Selectors. if the first argument passed is an element, it will select based on that element and all its children. Cool!

class MyHelperModuleTest < ActionView::TestCase
  include MyHelperModule

  test "MyHelperShouldReturnSomeHTML" do
    html = my_helper_function()
    assert_select html, "span.a_class", "test span's innerHTML here."
  end
end

Now, if you run rake test, the test will not work. Your helper probably returns a string, not an element. What the hell is an element? The answer is in the source: HTML::Node. This is not obvious.

Instantiating a new HTML::Node is kinda error prone. It needs a line number and a position number as well as a parent node. You want to avoid shooting yourself in the foot. HTML::Document is so much more welcoming anyway. Because most of my assertions will need to create an HTML::Document, I prefer using a protected method that create the document and returns the root node. Assert_select only cares about the node anyway.

class MyHelperModuleTest < ActionView::TestCase
  include MyHelperModule

  test "MyHelperShouldReturnSomeHTML" do
    html = my_helper_function()
    assert_select node(html), "span.a_class", "test span's innerHTML here."
  end

  protected

  def node(html)
    HTML::Document.new(html).root
  end
end

N.B: Make sure you change assert_select(html, …) with assert_select(node(html), …)

Now, if you run rake test, your test should pass! It’s all cool and dandy but what happens if your helper has arguments that are, for example, an ActiveRecord::Relation or an ActiveRecord::Base (a model)? You could use fixtures.

But remember, your models are supposed to be tested already. I submit to you that it’s pointless to use a full-fledged active record instance. I want to interact with your model, not test it. I know some uses mocha to mock your model but I don’t like using a gem to fill in a lack of knowledge. I rather use mocha or any other alternative when/if I am limited by my own implementations.

ActiveRecord::Base && ActiveRecord::Relation

I want to say it again: You can use fixtures. Actually, they are kinda necessary if you need to use relations inside your helper function. If you feel comfortable with them, use them. I use them in many cases, but in some other cases, I want to stub mock (Thanks latortuga) a model so it’s faster to execute, specially when I want to do many iterations quickly. Here’s how to do it.

Edit: Thanks to @f_bernier for pointing out Object#Allocate

class MyModelNumberOne < ActiveRecord::Base
end

class MyRelation < ActiveRecord::Relation
  def initialize
    @klass = MyModelNumberOne
    @loaded = true #If not true, it will try to fetch data from the database!
    @records = 10.times.map { MyModelNumberOne.allocate } # Mock the results.
  end
end

class MyHelperModuleTest < ActionView::TestCase
  include MyHelperModule

  test "MyHelperShouldReturnSomeHTML" do
    html = my_helper_function(MyRelation.new)
    assert_select node(html), "ul.relations" do |elements|
      elements.each do |element|
        assert_select element, "li", 10 # Do we have 10 li element?
      end
    end
  end

  protected

  def node(html)
    HTML::Document.new(html).root
  end
end

You have successfully mocked your model and tested your helpers. All this without using extra gems that would otherwise change your workflow!

If you liked this post, you should follow me on twitter!

Get more ideas like this to your inbox

You will never receive spam, ever.