Gavin Morrice

is a web and iOS developer from Edinburgh, Scotland.

more about me ยป

Writing Rails helper tests/specs using MiniTest::Spec

So MiniTest has been receiving a lot of attention recently as being the testing framework for Ruby. Supporting TestUnit and Rspec syntax it appeals to both camps while also offering a much lighter, faster codebase.

This week I bundled together a gem with a bunch of helper methods and CSS classes that I always seem to re-write across every app that I work in. Since the gem is still pretty basic and subject to change a lot before I settle on a stable version 1 I figured this would be a good project to experiment with MiniTest::Spec.

Moving to MiniTest::Spec

I prefer the Rspec flavour so named and structured my test files like so:

spec/
  - spec_helper.rb
  - dummy # a dummy rails app for testing
  - helpers
  - support # extra files for spec support, more on these in a minute 

Note - Using this structure, you'll also have to update your Rakefile a little:

# Rakefile
Rake::TestTask.new(:spec) do |t|
  t.libs << 'lib'
  t.libs << 'spec'
  t.pattern = 'spec/**/*_spec.rb'
  t.verbose = true
end

... and modify spec_helper.rb to look like this:

# Configure Rails Environment
ENV["RAILS_ENV"] = "test"

require File.expand_path("../dummy/config/environment.rb",  __FILE__)

require "rails/test_help"

require "minitest/rails"

Rails.backtrace_cleaner.remove_silencers!

# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

Creating a dummy class for the specs

Since Rails helpers are modules, we need to define a class to include the module in so we can test the methods. Your helper methods may also call Rails view helper methods so our class should behave just like a normal Rails view.

# spec/support/dummy_class.rb

# define a class that inherits from ActionView::Base
class DummyClass < ActionView::Base
  # include each of your Rails helpers here
  include MyHelper
end

Usually we access the helper through a method provided by rspec-rails named helper. We can recreate that same behaviour by defining this method:

# spec/spec_helper_methods.rb

def helper
  @helper ||= DummyClass.new
end

This method should now be available to all of our specs. We can call it like so:

require "spec_helper"

describe UsersHelper do

  describe :my_method do
    helper.my_method("arg").must_equal "<div class='someclass'>arg</div>"
  end

end

You can run your specs by running rake spec

Testing rails helper methods using MiniTest::Spec

The next problem I ran into was that, using MiniTest's must_equal I had to specify exactly what the output from the view helpers would be.

I find this is very brittle. For example, consider this method:

def bodacious_div(content, klass, id)
  %{<div class="#{klass}" id="#{id}">#{content}</div>}.html_safe
end

And this test:

  describe :bodacious_div do
    it "should return a div with class, id and content" do
      helper.bodacious_div("hello", "my_class", "my_id").must_equal %{<div id="my_id" class="my_class">#{content}</div>}
    end
  end

This test would fail because the ID and class attributes are in the wrong order. In other words, the method is behaving as I'd like it to but the test fails because the syntax isn't exact.

This is too brittle for my liking, I'd rather access a matcher like ActionController's assert_dom_equal.

To add a custom matcher, all we have to do is extend the MiniTest::Matchers module:

# spec/support/mini_test/assertions.rb
module MiniTest::Assertions

  def assert_dom_equal(expected, actual, message = nil)
    expected_dom = HTML::Document.new(expected).root
    actual_dom   = HTML::Document.new(actual).root
    message      = "expected:\n #{actual}\nto equal:\n#{expected}"
    assert_block(message) { expected_dom == actual_dom }
  end

end

This assertion isn't useful in Spec style MiniTests yet though, it's written in the UnitTest assertion syntax. To add it to the MiniTest::Spec expectations, we just need to call the method infect_an_assertion within MiniTest::Expectations

# spec/support/mini_test/expectations.rb

module MiniTest::Expectations

  infect_an_assertion :assert_dom_equal, :must_be_dom_equal, :reverse

end

The first argument refers to the assertion method we should call, the second argument is the name of the expectation we're defining. The reverse argument states whether or not we want to define an opposing expectation (wont_be_dom_equal)

So far, Miniest seems really cool. I'll posting more on this topic as I discover more in the near future.

If you'd like to check out the box_of_tricks gem, you can find it on github: github.com/KatanaCode/box_of_tricks