Skip to main content

Getting started with Ruby on Rails 3.2 and MiniTest - a Tutorial

For fun, I thought I would start a new Ruby on Rails project and use MiniTest instead of Test::Unit. Why? Well MiniTest is Ruby 1.9s testing framework dejour, and I suspect we will see more and more new projects adopt it. It has a built in mocking framework and RSpec like contextual syntax. You can probably get away with fewer gems in your Gemfile because of that.

Getting started is always the hardest part - let's jump in with a new rails project

rails new tddforme --skip-test-unit

Standard stuff. MiniTest sits nicely next to Test::Unit, so you can leave it in if you prefer. I've left it out just to keep things neat and tidy for now.

Now we update the old Gemfile:

group :development, :test do
  gem "minitest"
end

and of course, bundle it all up.....from the command line:

$ bundle

Note that if you start experiencing strange errors when we get in to the generators later on, make sure you read about rails not finding a JavaScript runtime. Fire up your rails server and check everything is hunky dory if you fancy.

A Model

For the purposes of testing our test framework, we are going to put together a small model. Let's call it DevelopmentMethodology and give it a couple of fields - perhaps a name and a description.

$ rails g model DevelopmentMethodology name:string description:string

Set up the test structure

Now we have a model and a migration. I like to write my first tests now. Yes I know, crazy, before I've even migrated......anyway, because I created my project with no test-unit, I don't even have a test directory, so I'll create it now along with a few other subdirectories that will mimic the usual testing structure

$ mkdir test
$ mkdir test/fixtures
$ mkdir test/unit

Now, inside the test directory, I'll set up a basic test_helper file:

$ touch test/test_helper.rb

The code for my test_helper is almost boilerplate - I'm putting the Rails Environment into "test" and loading up the Rails and then simply including the minitest framework.

ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require "minitest/autorun"

If you kept test-unit in your project, you'll have a test_helper already that will look similar to this, but won't yet bring in minitest. In this case you can choose to replace the "require" statement in that test_helper.rb file or you can create a new minitest_helper.rb file with the code.

Petit pause

Let's take a breath - go get a cup of coffee. We've covered a lot of ground. So far, the basic steps we have gone through are:

  • Create a new Rails project, skipping the test-unit framework
  • Update our Gemfile to include the minitest
  • Bundle
  • Create test directory structure
  • Create test_helper.rb

Writing a test

At last, we can write our first test! Create a new file in the test/unit directory - call it whatever you want, I've chosen to name mine "development_methodology_test.rb" and a basic outline goes like this:

require 'test_helper'

class TestDevelopmentMethodology < MiniTest::Unit::TestCase
  def test_our_test_framework_can_fail
    assert false
  end
end

First, we include our test_helper.rb. Then we define a class to test our DevelopmentMethodology model - it inherits from on of the classes in MiniTest. Easy peasy. Then, we create a method called test_our_test_framework_can_fail in which we simply assert false. Why would I bother to do this? Well, this little piece of code allows me to verify that I've set up MiniTest correctly - I haven't yet bothered to write any tests against my own application yet. However, when I run the test, I get valuable output (assuming everything has been set up correctly) and I can see that MiniTest is running and is producing a failing test:

[]$ ruby -Itest test/unit=development_methodology_test.rb
# Running tests:

F

Finished tests in 0.000601s, 1665.0543 tests/s, 1665.0543 assertions/s.

  1) Failure:
test_our_test_framework_can_fail(TestDevelopmentMethodology) [test/unit/development_methodology_test.rb:5]:
Failed assertion, no message given.

1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

I'll swiftly rewrite my test now that I know everything is working. I'm going to put a condition that a Development Methodology must contain a name.

require 'test_helper'

class TestDevelopmentMethodology < MiniTest::Unit::TestCase
  def test_development_methodology_has_name
    development_methodology = DevelopmentMethodology.new
    assert !development_methodology.save
  end
end

And run it

[]$ ruby -Itest test/unit=development_methodology_test.rb

# Running tests:

E

Finished tests in 0.032036s, 31.2147 tests/s, 0.0000 assertions/s.

  1) Error:
test_development_methodology_has_name(TestDevelopmentMethodology):
ActiveRecord::StatementInvalid: Could not find table 'development_methodologies'

It errors! But of course it does - we haven't run a migration yet. Let's do that and at the same time prepare our test database

$ rake db:migrate
$ rake db:test:load

Run our test again and you should get a failing test

[]$ ruby -Itest test/unit=development_methodology_test.rb

# Running tests:

E

Finished tests in 0.032036s, 31.2147 tests/s, 0.0000 assertions/s.

  1) Failure:
test_development_methodology_has_name(TestDevelopmentMethodology) [test/unit/development_methodology_test.rb:5]: Failed assertion, no message given.

1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

Refactor our test

The first thing I'm going to do is refactor my test - because MiniTest includes both assert and refute statements, I think my test could be more readable if I write it like this (of course, running the test again produces a failed test as above):

require 'test_helper'

class TestDevelopmentMethodology < MiniTest::Unit::TestCase
  def test_development_methodology_has_name
    development_methodology = DevelopmentMethodology.new
    refute development_methodology.save
  end
end

Make the test pass

Making this test pass is simple - we just have to validate the presence of name. Edit app/models/development_methodology.rb and add in our validates

class DevelopmentMethodology < ActiveRecord::Base
  attr_accessible :description, :name
  validates_presence_of :name
end

and now our test passes

$ ruby -Itest test/unit=development_methodology_test.rb
Run options: --seed 20660

# Running tests:

.

Finished tests in 0.139823s, 7.1519 tests/s, 7.1519 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

Make it purty

Now MiniTest comes with a lot built in - want your output to be in lovely colours - no problem - just run your tests like this and witness the beautiful and fabulous tests:

$ ruby -rminitest/pride -Itest test/unit=development_methodology_test.rb

Spec

A big plus point for MiniTest is the inclusion of spec by default. Watch what happens when we change our test to look like this:

require 'test_helper'

describe DevelopmentMethodology do
  it "must have a name" do
    development_methodology = DevelopmentMethodology.new
    development_methodology.save.must_equal false
  end

end

If you re-run your tests now, you get passing tests (obviously, because we've simply rewritten a passing test), but you've used some nice contextual RSpec like syntax all rolled up in MiniTest

That's good for today

I think that's enough of an introduction to using MiniTest with Rails. Hopefully, you've got some code to get you going so you can jump right in an explore MiniTest.

Comments

  1. If you use require_relative '../test_helper' you can save the -Itest and do not need to add another load-path to your app.

    Also check out minitest-reporters, it has a nice red-green 'Default' reporter, so you no longer need to read the output, it's either red or green ;)

    ReplyDelete
  2. Good tip with require_relative.....Thanks.

    I'll check out minitest-reporters

    ReplyDelete
  3. If you want a more immediate start to using MiniTest in a Rails app, check out minitest-rails.

    http://blowmage.com/2012/07/10/announcing-minitest-rails

    ReplyDelete

Post a Comment

Popular posts from this blog

Add css class to your label form helpers

Say, for example, you are using Twitter Bootstrap to style up a quick rails app and you want to throw in a label tag with the class set to "control-label" just like the bootstrap guys do.....how do you do it? And more importantly, since you don't want to specify the text of the label, how do you accomplish that? Fear not intrepid young but soon to be rails guru, behold the truth:

Overriding equality and Test Driven Development

Ruby has, at its root, an Object. Methods available in Object are available to every class because every class in Ruby inherits from Object somewhere in its own class hierarchy. Of course, you can override methods in subclasses, changing the functionality of a root method.You might stumble on to this idea if you work through Test Driven Development By Example by Kent Beck, translating the Java code into Ruby as you go. At some point pretty early on, he overrides the equality method on the Currency class to better test if two instances are equal. I'm going to do the same here, working with Instruments instead of Currency.EqualityEquality in Ruby can be expressed using any of the following three methods object == other equal?(other) eql?(other) These methods are defined on the base Object. The default implementation of equality will only return true if both objects are exactly the same. The interesting thing is that although these three methods start out functioning the same, the do…