Freelancing Gods 2014

God
11 Oct 2008

Flavouring For Your Specs

One of the things I’ve tried to do with Thinking Sphinx has been to keep it friendly for a few different versions of Rails/ActiveRecord: 1.2.6, 2.0.x and 2.1.×. It’s not that easy, because I know I don’t think about which methods are pre-Rails-2.0, and which are even newer. And add on top of that the large (and very welcome) number of contributors, and it becomes a bit of a headache.

I was introduced to a potential aid for this at RailsConf EU, by fellow Australian Ian White, in the form of his library Garlic. It takes Rails plugins, instantiates them within Rails applications for each defined environment, and then runs their specs.

That in itself is pretty damn cool – except I don’t run Thinking Sphinx’s specs from within Rails – and it’s really only reliant on ActiveRecord (so it can be used in Merb). So, I’ve spent an afternoon coding up something slightly different, but in something of an acknowledgement to Ian’s library, my solution is called Ginger.

Now, it’s not exhaustively tested, but I’ve got it to the point where it runs as I would expect it to with Thinking Sphinx, so consider it ready for release.

If you’re interested in using it, here’s the basic guidelines. First up, install the gem:

sudo gem install freelancing-god-ginger --source=http://gems.github.com

(GitHub doesn’t have the gem loaded at the moment though, so you’ll have to install it manually)

git clone git://github.com/freelancing-god/ginger.git
cd ginger
gem build ginger.gemspec
sudo gem install --local ginger-1.0.0.gem

Then, in your spec_helper.rb file (or equivalent – this should work for Test::Unit as well as RSpec), add:

require 'ginger'

You’ll want to make sure that require is before any other require calls to libraries you want to test multiple versions of.

Next, you need to create a scenario file, outlining the different testing situations Ginger should run through. These details go in a file called ginger_scenarios.rb in the root of your project. Here’s my current one for Thinking Sphinx:

require 'ginger'

Ginger.configure do |config|
  config.aliases["active_record"] = "activerecord"
  
  ar_1_2_6 = Ginger::Scenario.new
  ar_1_2_6[/^active_?record$/] = "1.15.6"
  
  ar_2_0_2 = Ginger::Scenario.new
  ar_2_0_2[/^active_?record$/] = "2.0.2"
  
  ar_2_1_1 = Ginger::Scenario.new
  ar_2_1_1[/^active_?record$/] = "2.1.1"
  
  config.scenarios << ar_1_2_6 << ar_2_0_2 << ar_2_1_1
end

I’m adding three different scenarios – one for each version of ActiveRecord I want to test. I’m also adding an alias, as sometimes people have an underscore in the require calls, and while the require method isn’t fussy, the gem method is. That’s also the reason for the regular expressions, but strings will work as well.

If you want, you can have multiple gem requirements for each scenario – Ginger::Scenario is subclassed from Hash, so just add more key/value pairs as needed.

sphinx_scenario = Ginger::Scenario.new
sphinx_scenario["riddle"] = "0.9.8" 
sphinx_scenario["thinking_sphinx"] = "0.9.8"

And don’t forget to add each scenario to @config@’s collection.

The last step is the most important – running your tests through each scenario! This is done with the ginger command line tool – any arguments given to it get echoed onto rake, so in theory it should work with whatever rake task you like. I’ve only tested it with RSpec though.

ginger spec
ginger test
ginger spec:unit

This was built to scratch my itch, obviously, but happy to accept patches or suggestions. GitHub, as always, is the best way to do this – fork to your heart’s content.

01 Jul 2008

Link: new controller examples

"refining the examples generated for restful controllers"

29 Apr 2008

Link: DanNorth.net » What’s in a Story?

Good overview of BDD, especially in relation to stories (feature in rspec)

13 Dec 2007

Rspec'ing controllers

I’m always trying to find a better way to write specs for my Rails apps with RSpec – it took a while for me to be comfortable with writing model specs, but just recently, I’ve developed a style of controller specs that I feel work well for me.

While it’s not too hard to write methods that automate some of the repetitive side of things, it can be hard to do so in a manner that fits RSpec’s DSL – but I’ve found the key to (my current style of) controller specs is shared behaviours. An example of a few actions from the news controller at ausdwcon.org:

describe NewsController, "index action" do
  before :each do
    @method = :get
    @action = :index
    @params = {}
  end
  
  it_should_behave_like "Public-Access Actions"
  
  it "should paginate the results" do
    @news = []
    News.stub_method(:paginate => @news)
    
    get :index
    
    News.should have_received(:paginate)
    assigns[:news].should == @news
  end
  
  it "should set the title to 'News'" do
    News.stub_method(:paginate => [])
    
    get :index
    
    assigns[:title].should == "News"
  end
end

describe NewsController, "new action" do
  before :each do
    @method = :get
    @action = :new
    @params = {}
  end
  
  it_should_behave_like "Admin-Only Actions"
  
  it "should set the title to 'News'" do
    controller.current_user = User.stub_instance(:admin? => true)
    
    get :new
    
    assigns[:title].should == "News"
  end
end

You can find the full spec for the controller on pastie.caboo.se. The shared behaviors – ‘Public-Access Actions’ and ‘Admin-Only Actions’ – are (at least for the moment) kept in my spec_helper.rb file – a sample of which is below:

describe "Admin-Only Actions", :shared => true do
  it "should not be accessible without authentication" do
    @controller.current_user = nil
    
    send @method, @action, @params
    
    response.should be_redirect
    response.should redirect_to(new_session_url)
  end
  
  it "should not be accessible by a normal user" do
    @controller.current_user = User.stub_instance(:admin? => false)
    
    send @method, @action, @params
    
    response.should be_redirect
    response.should redirect_to(new_session_url)
  end
  
  it "should be accessible by an admin user" do
    @controller.current_user = User.stub_instance(:admin? => true)
    
    send @method, @action, @params
    
    response.should_not redirect_to(new_session_url)
  end
end

Firstly – the not so nice stuff: the use of instance variables to communicate the method, action and parameters of requests to the shared behaviours. It’s not ideal, and apparently there’s plans to add arguments to the it_should_behave_like method, but for the moment it does the job.

I’m using Pete Yandell’s NotAMock for my stubbing – albeit with a few modifications of my own (which may make it back into the plugin itself at some point). I also use my own ActiveMatchers – but that’s more focused on models. It’s also not really feature-complete, but if you like what it offers, feel free to use it.

Oh, and the main caveat? This is my current way of spec’ing controllers – and it’s vastly better than the minimal specs I was writing before this – but it may/will change. I don’t even know if my style is ‘best practice’ – I’m putting them online to get feedback and provoke discussion. So please feel free to critique.

05 Dec 2007

Link: Plain Text Stories: Part III

Examples of the new stories/integration testing in rspec

29 Aug 2007

Link: smartbomb - Hornsby

Scenario-focused spec'ing, instead of fixtures

15 Mar 2007

Link: Developing a Rails model using BDD and RSpec, Part 1 — Luke Redpath

RssSubscribe to the RSS feed

About Freelancing Gods

Freelancing Gods is written by , who works on the web as a web developer in Melbourne, Australia, specialising in Ruby on Rails.

In case you're wondering what the likely content here will be about (besides code), keep in mind that Pat is passionate about the internet, music, politics, comedy, bringing people together, and making a difference. And pancakes.

His ego isn't as bad as you may think. Honest.

Here's more than you ever wanted to know.

Ruby on Rails Projects

Other Sites

Creative Commons Logo All original content on this site is available through a Creative Commons by-nc-sa licence.