RSpec on Rails: Models

10 Aug 2007

In my last post I talked about using RSpec without Rails, but since just about all my Ruby programming involves Rails, I should probably get into how to specify Rails code.

RSpec on Rails: Models

So if you’re new to RSpec and you want to get started quickly, head on over to rspec.rubyforge.org, get RSpec, Spec::Rails and then, after you’ve created a new Rails project and rspec-ed it (all detailed on the website) you can run:

ruby script/generate rspec_scaffold person name:string
        phone_number:integer
        cash:decimal

Which will get you a bunch of stuff to play with. Of you’ll need to set up your database.yml and run rake db:migrate if you want thing to work.

Fire up your favorite IDE and find the person_spec.rb and you should see something like:

require File.dirname(__FILE__) + '/../spec_helper'

describe Person do
 before(:each) do
   @person = Person.new
 end

 it "should be valid" do
   @person.should be_valid
 end
end

Not the world’s most rigorous set of specs, but then our model doesn’t have much behavior at the moment. You’ll notice that the auto-generated spec does use RSpec’s built in interrogative feature. ‘be_valid’ makes RSpec look for ‘valid?’ Running the spec with spec formatting (‘-fs’) produces the following output:

Person
- should be valid

Finished in 0.028878 seconds

1 example, 0 failures

But what’s interesting is that if I remove the string between it and do, the output remains the same. If you leave off the name of a spec, RSpec guesses a name for you by looking at what you’re asserting.

But let’s say I want to specify that a Person is invalid without a name. Here’s how I would do that:

 it "should be invalid without a name" do
   @person.should have(1).error_on(:name)
   @person.should_not be_valid
 end

As you can see, there’s some built in sugar to make checking errors more readable. Now that I have a failing spec I can add the following line to my Person model to make the it pass:

 validates_presence_of :name

Now let’s do something a little more interesting:

 it "should run inside a transaction when making rich" do
   Person.should_receive(:transaction).and_yield
   @person.make_rich
   @person.cash.should == 1000000.00
 end

What I’m saying here is that a Person should have a method that makes them rich and furthermore that the rich-making should happen inside a transaction. RSpec has mature mocking/stubbing framework built into it. ‘and_yield’ can even specify what to yield so you could return an object that you created in the spec and then check to make sure some things happened to it. Of course, before I get to much further, I should write some code to make the spec pass:

 def make_rich
   Person.transaction do
     self.cash = 1000000.00
   end
 end

Now what if you already have a codebase that has a lot of Test::Unit tests but you want to start using rspec? Well you could just start writing specs in their own spec folder and rspec’s default rails task will run both your tests and your specs.

Next time we’ll look at view specing with RSpec.

Future note: No we won’t and don’t do that - 2018/09/10