Friday, 21 September 2012

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.

3 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