Tutorial: Rails with Rspec, Spork and Guard From Scratch

Getting set up to test a Rails app with Rspec is relatively straightforward. One of the troubles is the length of time it takes for the tests to run when issuing either the rspec spec or rake spec commands from the command line. My current tests for example take about ¡16 seconds! to complete. Also, who wants to switch back to the command line to run the tests. What if we could automating the tests and speed them up at the same time? Enter Guard and Spork.

Guard is a command line tool that, when running, responds to events when files are modified. We can start Guard and have it observe certain files for changes, then run the Rspec commands in response. Using Spork, we can in effect pre-load the Rails environment into the Spork test server. Spork manages a pool of test processes for you so each test is clean (but fast because of the pre-load).

You can add these items to your existing rails app, but I’m going to take us through doing this from scratch (mainly so I can have documentation for this myself for future apps!).

Get Started

I’ve created a demo app to go along with this article. You can grab it on github at https://github.com/typeoneerror/Guard-Spork-Demo or create your own.

Start by creating a Rails project and moving into it:

$ rails new guard-spork
$ cd guard-spork

We only need a handful of gems and a config file or two to get this going. Let’s start by editing our Gemfile to add Rspec for Rails and Spork (this file shown with the initial comments removed):

source 'http://rubygems.org'

gem 'rails', '3.0.9'

gem 'sqlite3'

group :development, :test do
  # Rspec
  gem 'rspec-rails', '~> 2.6'

  # Spork
  gem 'spork'
end

Add the gems to your Gemfile and then run

bundle install

The ~> syntax is an interesting one. Essentially, this is equivalent to >= 2.6 AND < 2.7. So it’ll install the latest version of the 2.6 (so 2.6.5 would install if that was the latest, but it would not update to 2.7).

Anyway, now that the gems are installed, we need to bootstrap Rspec. Command line again:

$ rails generate rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb

I then add the following snippet to config/application.rb to tell the Rails app to use Rspec as my test generator:

config.generators do |g|
  g.test_framework :rspec
end

Running rake -T spec at this point should display a series of rake task for rspec. You can skip the rake way of doing it and use Rspec directly though. Let’s create a sample model to test.

$ rails g model Article title:string
      invoke  active_record
      create    db/migrate/20110814231432_create_articles.rb
      create    app/models/article.rb
      invoke    rspec
      create      spec/models/article_spec.rb

Note that it generated (g is an alias for generate) the model, a database migration and automatically added a spec for testing that model. Neat!

Let’s create our database (sqlite3 since we’re just doing a demo which is the default Rails database):

$ rake db:migrate
==  CreateArticles: migrating ================
-- create_table(:articles)
   -> 0.0011s
==  CreateArticles: migrated (0.0012s) =======

Great, we’ve got our initial database. Let’s add a simple validation for that model:

# app/models/article.rb
class Article < ActiveRecord::Base
  validates :title, :presence => true
end

Just for example’s sake, let’s add two simple (and largely pointless) tests to our spec in spec/models/article_spec.rb:

require 'spec_helper'

describe Article do

  it 'should test some silly thing that will pass' do
    @article = Article.new(:title => 'The Title')
    @article.should be_valid
  end

  it 'should test some silly thing that will fail' do
    @article = Article.new
    @article.should be_valid
  end

end

Now you can run

$ rpsec spec

to test your app (this will run all your specs). Even with just these ridiculously simple tests, we can run

$ time rpsec spec
real    0m3.017s

and this thing is taking 3 seconds already! Imagine what happens when you’ve got hundreds of tests.

Spork

So let’s see what Spork can do for us. Start by bootstrapping Spork from the command line:

$ spork --bootstrap
...
Using RSpec
Bootstrapping spec/spec_helper.rb.
Done. Edit spec/spec_helper.rb now with your favorite text editor and follow the instructions.

Looks like we should edit spec/spec_helper.rb with our favorite text editor. Spork added a few blocks. In summary, anything in the Spork.prefork block gets pre-loaded and anything in the Spork.each_run block will be done on each test run.

I’m basically going to grab everything that was previously in this file (the Rspec stuff that should not be towards the bottom of this file) and drop it into the prefork block:

require 'rubygems'
require 'spork'

Spork.prefork do

  # This file is copied to spec/ when you run 'rails generate rspec:install'
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  RSpec.configure do |config|
    # Mock Framework
    config.mock_with :rspec

    # If you're not using ActiveRecord, or you'd prefer not to run each of your
    # examples within a transaction, remove the following line or assign false
    # instead of true.
    config.use_transactional_fixtures = true
  end

end

Spork.each_run do
  # This code will be run each time you run your specs.
end

We’re also going to set up Rspec to use Spork DRB server. Open up .rspec and add the following:

--colour
--drb
--format documentation

Next start Spork in a new Terminal window (in your project directory):

$ spork
...
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!

Basically, now we’ve got Spork DRB running and now when we run Rspec, it’s configured to use the running DRB server. With Spork running, switch back to your project and run Rspec again:

$ time rspec spec
real    0m0.499s

Wow. We’ve basically decreased our test runtime by ~85%. You can just leave Spork running until you’re finished working then send it a ^C command to quit.

Should we take a break? Nah, let’s get this testing thing automated so we can take more breaks. A lazy developer is a good developer, right?

Guard

Next, let’s add Guard and some of its really cool “extensions” that allow us to “guard” (or watch and respond to changes in) resources. Edit your Gemfile (this is where our Gemfile starts to get kinda wordy):

source 'http://rubygems.org'

gem 'rails', '3.0.9'

gem 'sqlite3'

group :development, :test do
  # Rspec
  gem 'rspec-rails', '~> 2.6'

  # Spork
  gem 'spork'

  # Guard
  gem 'growl'
  gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
  gem 'guard-bundler'
  gem 'guard-rspec'
  gem 'guard-spork'
end

Taking a look at our Guard includes now, you can see I’ve added guard-rspec. This gem bundles guard as a dependency, so there’s no need to include the guard gem itself. I’ve also included the “guards” for bundler and spork. rb-fsevent is required to respond to changes in a directory tree. Keep in mind, this is how to do this on Mac OSX, so if you need a different platform, the install instructions for Windows and Linux on Github are quite straightforward.

Growl can be added as well if you want to show Growl messages when your tests finish running. I find this pretty useful, but a lot of people can’t stand Growl. Keep in mind you have to install the growlnotify extra from the Growl .dmg for this gem to work. Note that it took me awhile to get growl going. guard-rpsec notes using the growl gem and guard recently changed this to growl_notify, so it seems pretty volatile. Growl seems to work great if you’re just running the Rspec guard, but I haven’t been able to get it to notify me when running Rspec through Spork. Please message me on Twitter if you have a way of doing this.

Back to command line; let’s install the next set of gems:

$ bundle install

Next generate a Guardfile via the command:

$ guard init
Writing new Guardfile to guard-spork/Guardfile

Staring guard is easy, but first we have to create some guards in our Guardfile. Here’s a starter file for you that I am using:

guard 'spork', :cucumber => false, :test_unit => false do

  watch('config/application.rb')
  watch('config/environment.rb')
  watch(%r{^config/environments/.+.rb$})
  watch(%r{^config/initializers/.+.rb$})
  watch('spec/spec_helper.rb')

end


guard 'bundler' do

  watch('Gemfile')
  # Uncomment next line if Gemfile contain `gemspec' command
  # watch(/^.+.gemspec/)

end


guard 'rspec' do

  watch(%r{^spec/.+_spec.rb$})
  watch(%r{^app/(.+).rb$})                           { |m| "spec/#{m[1]}_spec.rb" }
  watch(%r{^lib/(.+).rb$})                           { |m| "spec/lib/#{m[1]}_spec.rb" }
  watch(%r{^spec/factories/(.+).rb$})                { "spec" }
  watch(%r{^spec/models/.+.rb$})                     { "spec/models" }
  watch(%r{^spec/routing/.+.rb$})                    { "spec/routing" }

  watch('spec/spec_helper.rb')                        { "spec" }
  watch('config/routes.rb')                           { "spec/routing" }
  watch('app/controllers/application_controller.rb')  { "spec/controllers" }

end

The above configures each guard (remember we installed the -rspec, -spork and -bundler guard gems?) to watch certain files (by path or regular expression matching) and perform actions when those files change. For example, in our Rspec guard, we’re watching spec/spec_helper.rb and when it changes “spec” is going to be called. So basically, save a spec and your tests will be run automatically.

The bundler guard file watches your Gemfile for changes and bundle install’s when it is saved. And now the Spork server is restarted any time we make configuration file changes. Guard even starts Spork for us so we no longer have to issue the spork command at the command line, just:

$ guard
...
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
Spork server for RSpec successfully started
...
Refresh bundle
Your bundle is complete!
...
Guard::RSpec is running, with RSpec 2!
Running all specs
...
Finished in 0.10119 seconds
2 examples, 1 failure

So there you have it; guard is running, listening for changes and Spork has started and has our Rspec/Rails stuff pre-loaded. If you go an edit spec/models/article_spec.rb now…

require 'spec_helper'

describe Article do

  it 'should test some silly thing that will pass' do
    @article = Article.new(:title => 'The Title')
    @article.should be_valid
  end

  it 'should test some silly thing that will now pass' do
    @article = Article.new
    @article.should_not be_valid
  end

end

…guard will note the changes and run spec to run your tests. Everything should now pass.

So there you have it. Your tests should now run pretty damn fast. Full disclosure, this is my first time working with Rails and gems so I may have done some things somewhat ass-backwards. I’d appreciate any comments you might have to that effect on my Twitter account. Please don’t hesitate to send me feedback there.

August 14th, 2011 | Permalink

PHPUnit Follow-up: Code Coverage with Xdebug on Mac

It took me considerable amount of time to figure out the correct file to edit in order to have Xdebug work from the command line with PHPUnit's --report flag. This flag generates HTML files that look something like the following image:

code coverage of ASRA 2.0 with Xdebug

This displays the number of lines that have been run in the process of unit testing; and in a lovely readable format (see Xdebug's normal format to see how much more useful this is). To get PHPUnit and Xdebug working, start by following the tutorial in the previous article. Then to get Xdebug running, check out this post by Felx Geisendörfer. Follow steps 1 + 2. If you are using regular-old MAMP, 3 + 4 should get you going just fine. The problem I had was I am using MAMP Pro, so I was editing my "template" .ini file thinking that was where the lines needed to go. Xdebug was loaded as a browser module, but was not loaded when I ran the coverage report in PHPUnit. So, in Pro, you have to add the config options to /Applications/MAMP/conf/php5. This is the ini file that the CLI version of PHP uses.

Here are the lines I added to get it working. Note that you may also need to disable the ZendExtensionManager and the zend_optimizer extension for it to work..in MAMP just uncheck the box. Check the compatibility section on the install page for more.

[Zend]
;zend_optimizer.optimization_level=15
;zend_extension_manager.optimizer=/Applications/MAMP/bin/php5/zend/lib/Optimizer-3.3.3
;zend_optimizer.version=3.3.3
;zend_extension=/Applications/MAMP/bin/php5/zend/lib/ZendExtensionManager.so

[xdebug]
zend_extension=/Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20050922/xdebug.so
xdebug.remote_enable = 1

If you completed the other article, PHPUnit should be using MAMP's php, which should now have Xdebug rocking and you can do something like:

# 'report' is the name of the folder the html will output to
$ phpunit --report report My_Test 
November 15th, 2009 | Permalink

Installing and Using PHPUnit with MAMP

Unit testing is becoming more and more popular in every day web development. I decided to see what all the fuss was about. So here's a quick tutorial on getting PHPUnit up and running with MAMP on OSX.

First you need to grab a PHPUnit release. You need a command line utility called pear-phpunit to run tests and also the PHP files that make up the framework. I recommend first doing an svn export somewhere to get the command line script and then doing another export to just export the framework library.

MAMP's default include path is /Applications/MAMP/bin/php5/lib/php and the Pear installation is there already so that's where you should install PHPUnit. So, let's start by grabbing the PHPUnit Framework:

$ cd /Applications/MAMP/bin/php5/lib/php
$ svn export svn://svn.phpunit.de/phpunit/phpunit/branches/release/3.3/PHPUnit
# ...
# Exported revision 4387.

You can use their Trac Browser to decide which release you want to work with. If you ever want to upgrade, you can just delete this folder and export a different release.

In the resulting folder, open PHPUnit/Util/Fileloader.php and do a find and replace for @php_bin@ with the path to your PHP command-line interpreter which for php5 in MAMP should be /Applications/MAMP/bin/php5/bin/php.

Next export or download the command line script:

svn export svn://svn.phpunit.de/phpunit/phpunit/branches/release/3.3/pear-phpunit

Open pear-phpuint and replace @php_bin@ with /Applications/MAMP/bin/php5/bin/php (same as the earlier file).

Next, copy the pear-phpunit executable to a location that exists in your unix PATH environment variable. My PATH already included a /usr/local/bin and I had installed a different version of Pear there earlier so I went ahead and moved it there (you may have to create a directory or put it in another directory in your path). At this point you should rename the file to "phpunit" as well. You can accomplish this via command line or (as I like to sometimes do) go to Finder > Go > Go To Folder... and type in /usr. This will allow you to use the OS GUI to copy the file over and rename it (providing you enter your password). Now you have to make the file executable to be able to run it:

sudo chmod +x /usr/local/bin/phpunit

So at this point you should have:

  • a) The PHPUnit framework in /Applications/MAMP/bin/php5/lib/php/PHPUnit
  • b) The phpunit executable moved, renamed and chmod'ed in /usr/local/bin/phpunit

If everything worked correctly, you should be able to type "phpunit" anywhere and see the "useage" information. I used the following link to set this up, so refer to that if you have any other troubles.

Here's my first couple tests for testing the Json export class in the in-progress Asra 2.0. Though I would not see myself doing this for "normal" websites, I can see how useful it is in application development. I already found a missing required class and a possible bug in just creating this simple test case.

<?php

/**
* test setup ~ /tests/Tests.php
*/
require_once dirname(__FILE__) . '/../../../Tests.php';

/**
* @see Asra.Export
*/
require_once 'Asra/Export/Data.php';
require_once 'Asra/Export/Format/Json.php';

/**
* @see PHPUnit_Framework
*/
require_once 'PHPUnit/Framework.php';

class Asra_Export_Format_JsonTest extends PHPUnit_Framework_TestCase
{
    /**
     * assert that export returns json and is valid json
     */
    public function testExportData()
    {
        $array = array("hello, world!", "hello, again!");
        $data = new Asra_Export_Data($array);
        $format = new Asra_Export_Format_Json($data);
        $this->assertNotNull($format->export());
        $this->assertNotNull(json_decode($format->export()));
    }

    /**
     * assert that passing a non-array to format throws an exception
     */
    public function testDataNotArray()
    {
        //$this->setExpectedException('Asra_Export_Exception');
        $data = "This is not an array!";
        try
        {
            $format = new Asra_Export_Format_Json($data);
        }
        catch (Asra_Export_Exception $e)
        {
            return;
        }
        $this->fail("An expected exception has not been raised.");
    }
}
July 20th, 2009 | Permalink