RSpec on Rails: Models

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. But that's not your only option. You can use RSpec inside your tests like so:

require File.dirname(__FILE__) + '/../../test_helper'
require 'spec/test_case_adapter'

class PersonTest < person =" Person.new(:name"> "Jake")
end

def test_person_can_have_name_set
@person.name.should == "Jake"
end
end

Now if you do this you won't have access to all the feature of RSpec: The reports, the mocking frameworks, and some custom matchers won't work (for instance '@person.should have(1).error_on(:name)' isn't available) but it's a low impact way of trying out RSpec before you commit.

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

Comments

Popular posts from this blog

Point Inside a Polygon in Ruby

What's a Good Flog Score?

SICP Wasn’t Written for You