Rails Automated Testing Setup for Beginners

Published: 2014-08-27

When you first start out with Rails, the sooner you learn testing, the sooner you will have faith that your code is working and that changes you are making or adding to the code aren't breaking your application.

Whatever your approach to testing, setting up automated tests will help a lot. If you are going to do TDD (test driven development) or just write your tests as you go along, then having your tests run when files have been modified will help a lot.

For my approach to testing in Rails, I have always used Test::Unit and now MiniTest. I use Guard to automate Minitest.

Here is what I do to quickly get testing added to any Rails project I am working on.

NOTE: The following versions were used for this setup:

  • Rails: 4.1.7 (previous: 4.1.5)
  • Ruby 2.1.4 (previous: 2.1.2)
  • MiniTest 5.4.3 (previous: 5.4.0)

Installing Guard

First add the following code to your Gemfile. See below if you are on OS X Yosemite.

# guard testing
group :development do
  gem 'guard-minitest', '~> 2.3.2' # https://github.com/guard/guard-minitest
  # Colorize minitest output and show failing tests instantly.
  gem 'minitest-colorize', git: 'https://github.com/ysbaddaden/minitest-colorize' 
  # https://github.com/Springest/terminal-notifier-guard
  gem 'terminal-notifier-guard', '~> 1.5.3' 
end
Now run: bundle install to install the Guard gems.

OS X Yosemite users, can use the following:

group :development do
  gem 'guard-minitest', '~> 2.3.2' # https://github.com/guard/guard-minitest
  # Colorize minitest output and show failing tests instantly.
  gem 'minitest-colorize', git: 'https://github.com/ysbaddaden/minitest-colorize'
  gem 'terminal-notifier-guard', '~> 1.6.4' # https://github.com/Springest/terminal-notifier-guard
  gem 'terminal-notifier', '~> 1.6.2' # https://github.com/alloy/terminal-notifier
end
You will need to install terminal-notifier with: brew install terminal-notifier

What does each gem do?

  • guard-minitest - This is the core gem that installs Guard and is setup to watch files and call actions when they change.
  • minitest-colorize - Will 'Colorize minitest output and show failing tests instantly'. This is a lot easier to see at a glance if your tests are passing or failing. The official gem doesn't currently work with MiniTest 5.2 and above, so I found this fork and it works great. Hopefully one day it will get released as a proper gem.
  • terminal-notifier-guard - This is the notifier for Mac OS X 10.8 and above. When a test is run, a notification will pop up with the pass/fail status. This way you never have to switch back to your terminal window.

NOTE: The gems are in the development group because they are run from development environment and don't affect the testing environment.

Setting up Guard

Now you should have Guard installed. Next is to generate a config file. Run this on the command line.

bundle exec guard init minitest

This will generate a default Guardfile in the root of your Rails app. What the Guardfile does is tell Guard what files to watch and what to do when a file changes. In our case it is going to watch the specific Rails app folders and files and then call the relevant test files using MiniTest.

Here is what the file should look like:

# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard :minitest do
  # with Minitest::Unit
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
  watch(%r{^lib/(.*/)?([^/]+)\.rb$})     { |m| "test/#{m[1]}test_#{m[2]}.rb" }
  watch(%r{^test/test_helper\.rb$})      { 'test' }

  # with Minitest::Spec
  # watch(%r{^spec/(.*)_spec\.rb$})
  # watch(%r{^lib/(.+)\.rb$})         { |m| "spec/#{m[1]}_spec.rb" }
  # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }

  # Rails 4
  # watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
  # watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  # watch(%r{^app/views/(.+)_mailer/.+})                   { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  # watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
  # watch(%r{^test/.+_test\.rb$})
  # watch(%r{^test/test_helper\.rb$}) { 'test' }

  # Rails < 4
  # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
  # watch(%r{^app/helpers/(.*)\.rb$})     { |m| "test/helpers/#{m[1]}_test.rb" }
  # watch(%r{^app/models/(.*)\.rb$})      { |m| "test/unit/#{m[1]}_test.rb" }
end

Get the Guardfile ready for Rails 4

Remove the comments from the lines below the Rails 4 entry and delete the rest so your file looks like this:

# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard :minitest do
  # with Minitest::Unit
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
  watch(%r{^lib/(.*/)?([^/]+)\.rb$})     { |m| "test/#{m[1]}test_#{m[2]}.rb" }
  watch(%r{^test/test_helper\.rb$})      { 'test' }

  # Rails 4
  watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
  watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                   { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
  watch(%r{^test/.+_test\.rb$})
  watch(%r{^test/test_helper\.rb$}) { 'test' }
end

What each line does

guard :minitest do
  # ...
end
This tells Guard that the following block is to be run with Minitest. You can configure Guard with multiple plugins that can do more than just run tests.

watch(%r{^test/(.*)\/?test_(.*)\.rb$})
This line watches all the .rb files in the test/ sub folders and will run that file if it changes (this is what the no arguments mean).
Notice how that doesn't include test_helper.rb as it isn't in a sub folder. test_helper.rb is watched with another line.

watch(%r{^lib/(.*/)?([^/]+)\.rb$})     { |m| "test/#{m[1]}test_#{m[2]}.rb" }
This watches the .rb files under the lib directory and will then run a corresponding test file if there is a match.
Example:
File: lib/shell/remote_shell.rb
Test File: test/shell/test_remote_shell.rb
If the test file didn't exist then no test would be run.

watch(%r{^test/test_helper\.rb$})      { 'test' }
This will just watch the test_helper.rb file and if that is changed, then all tests are run.

watch(%r{^app/(.+)\.rb$})  { |m| "test/#{m[1]}_test.rb" }
This watches the .rb files in the app directory and runs the corresponding tests. In a default Rails site, this would be models, controllers, helpers and mailers.

watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
This watches th application_controller.rb file and runs all the controllers tests if it is changed.

watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
This watches the controllers and then runs the corresponding integration test.

watch(%r{^app/views/(.+)_mailer/.+})                   { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
Watches the mailer views folder and then runs the mailer test for that mailer if one of its views change.

watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
Watches any .rb file in the lib folder and runs a corresponding test if it exists.

watch(%r{^test/.+_test\.rb$})
Watches the test folder for any files ending in _test.rb and runs them if they change.

watch(%r{^test/test_helper\.rb$}) { 'test' }
Duplicate of the test_helper.rb watcher from before. Can actually be deleted.

What about the Guardfile?

Guard will also watch the Guardfile, and if it changes, then it will re-evaluate the file and run all tests.

Enhancing your Guard file

So now you know how it the Guardfile works, you probably have a few ideas about extra lines you can add.
Here are the tweaks that I use that you can add to the Guard file to get it to track views and fixture changes.

# A sample Guardfile
# More info at https://github.com/guard/guard#readme
require 'active_support/inflector'

guard :minitest do
  # with Minitest::Unit
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
  watch(%r{^lib/(.*/)?([^/]+)\.rb$})     { |m| "test/#{m[1]}test_#{m[2]}.rb" }
  watch(%r{^test/test_helper\.rb$})      { 'test' }

  # Rails 4
  watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
  watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                   { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
  watch(%r{^test/.+_test\.rb$})
  watch(%r{^test/test_helper\.rb$}) { 'test' }
  # extra tests
  watch(%r{^app/views/(.+)/.+}) { |m| "test/controllers/#{m[1]}_controller_test.rb" }
  watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/models/#{m[1].singularize}_test.rb" }
  watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/controllers/#{m[1]}_controller_test.rb" }
end
NOTE: Don't forget the require line at the top of the file.

The breakdown of the extra tweaks

require 'active_support/inflector'
Here we add the Rails inflector class so we can 'singularize' fixture names further down. This allows us to turn 'Posts' into 'Post' for any models or controllers.

watch(%r{^app/views/(.+)/.+}) { |m| "test/controllers/#{m[1]}_controller_test.rb" }
This will run the controller test if any of the view files change.

watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/models/#{m[1].singularize}_test.rb" }
end
This will run the model tests if the fixtures change. This is where we use the Rails inflector class.

watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/controllers/#{m[1]}_controller_test.rb" }
This will run the controller tests if the fixtures change. This is where we use the Rails inflector class.

Running your Guard file

Now it is a simple matter of running Guard on the command line

bundle exec guard
This will start Guard up, have it parse the Guardfile and any plugins you have installed and then run all your tests.

You should see output similar to the following:

16:49:28 - INFO - Guard is using TerminalNotifier to send notifications.
16:49:28 - INFO - Guard is using TerminalTitle to send notifications.
16:49:28 - INFO - Guard::Minitest 2.3.2 is running, with Minitest::Unit 5.4.0!
16:49:28 - INFO - Running: all tests
Run options: --seed 48505

# Running:

.......

Finished in 1.221098s, 5.7325 runs/s, 10.6462 assertions/s.

7 runs, 13 assertions, 0 failures, 0 errors, 0 skips

16:49:33 - INFO - Guard is now watching at '/test_apps/minitest'
[1] guard(main)>

Now whenever you make a change to your watched files your relevant test file will run and notify you of the result.
To manually run all tests, just press 'Enter' at the Guard prompt

What next?

The Next article in the series is: Rails Unit Testing with MiniTest for Beginners

Resources

RailsGuides for Validations - The Rails Guides guide to testing applications.

Guard repo on GitHub - The core Guard gem handles events when a file system is modified.

Guard Wiki on system notifications - Outlines all the ways Guard can notify you.



comments powered by Disqus