n.times { code! }

Friday, December 07, 2007

Rails 2.0 released

Although there is no info about this in the rails blog, I did a gem update this morning, anv voilĂ :


papipo$ sudo gem update
Updating installed gems...
Bulk updating Gem source index for: http://gems.rubyforge.org
Attempting remote update of actionmailer
Install required dependency actionpack? [Yn]
Install required dependency activesupport? [Yn]
Successfully installed actionmailer-2.0.0
Successfully installed actionpack-2.0.0
Successfully installed activesupport-2.0.0

And it keeps going with all 2.0 libs.

Greetings for the rails team.

Enjoy 2.0.

Thursday, October 11, 2007

UnitRecord and rails 2.0 PR

I've been reading about another way of testing, and I thought that I should give it a try.
The problem is that when you setup all the needed steps, UnitRecord complains about ActiveRecord trying to connect to the database:


[...]
RuntimeError: ActiveRecord is disconnected; database access is unavailable in unit tests.
[...]

This is due to some changes in rails trunk, that added a "reset_cache" method that checks for an ActiveRecord connection.

In order to avoid this, you can change your unit_test_helper.rb from this (as shown in UnitRecord documentation):

require File.dirname(__FILE__) + "/../test_helper"
require "unit_record"
ActiveRecord::Base.disconnect!

to this:

require File.dirname(__FILE__) + '/../test_helper.rb'
require 'unit_record'

module UnitRecord
module DisconnectedFixtures
def disconnect!
(class << self; self; end).class_eval do
def create_fixtures(*args); end
def reset_cache(*args); end
end
end
end
end

ActiveRecord::Base.disconnect!

That will take care of errors.
Happy testing!

Thursday, August 30, 2007

BDD, isolation, integration

I have been reading a lot of posts about skinny controllers, isolation and good behaviour-driven development. Let me explain from the beginning:

The typical approach to spec a 'create' action looks like this (Taken from rspec documentation):


describe PeopleController do
it "with a valid person should redirect to index on successful POST to create" do
@person.should_receive(:new_record?).and_return(false)
Person.should_receive(:create).with({"name" => 'Aslak'}).and_return(@person)

post 'create', {:person => {:name => 'Aslak'}}

response.should redirect_to(:action => 'index')
end
end


That is too tied to the implementation. What if I want to use the 'create' method in my controller? And what about assigning attributes in a block?
What you are spec-ing here, is how your controller should look, not how it should behave.

Then I saw this in one of the blogs that I linked above:

it "should create a new thing" do
lambda { do_post }.should change { Thing.count }.by(1)
end


This is really testing behaviour. The main problem with this (although I really liked it the first time I saw it) is, of course, that interacts with the database and the models. I don't want to hit the database from my controller specs at all, I want full isolation.

Add a pair of helpers in spec_helper.rb:

def mock_valid_model(klass)
mock_model(klass, :save => true, :save! => true)
end

def mock_invalid_model(klass)
m = mock_model(klass, :save => false)
m.stub!(:save!).and_raise(ActiveRecord::RecordNotSaved)
m
end

Then stub Model.new to return the mock you want. Spec your controller 'create' action in order to redirect in case that the mock is valid, and to render a template if it is not valid (of course you can add specs for flash or whatever):


describe ThingsController, '"create" a valid model by POST' do
before do
@thing = mock_valid_model(Thing)
Thing.stub!(:new).and_return(@thing)
end

it 'should redirect to model show' do
post :create
response.should redirect_to(thing_url(@thing))
end
end

describe ThingsController, '"create" an invalid model by POST' do
before do
@thing = mock_invalid_model(Thing)
Thing.stub!(:new).and_return(@thing)
end

it 'should render "create" template' do
post :create
response.should render_template('things/create')
end
end

There are various interesting things here:
  1. You don't need to keep adding expectations to your mocks. Expectations couple your specs with the implementation. With my approach you can use create, new + save, new + block assignement, whatever. And the spec still passes.
  2. The database isn't touched at all. That's good stuff, I guess.
  3. You are really testing the behaviour. You know that the only way your controller will know if it should redirect or render, is to create a new instance of Thing, and call save or save!. That is actually an implied expectation, but without the need of should_receive(), just stubs.
Some of you may have noticed that I don't need to use the params() hash in the controller in order to make the specs pass. Right. But you know, that your controller is creating a new instance of Thing, and calling save, save!, create or whatever. That's when integration/acceptance tests come in

The only moment that you want your controller mess with params, is when you are not isolating. Because, if you are isolating, you can't validate parameters, since you stubbed save and save!.

With this approach, you keep your specs to a minimum amount of lines, you isolate, you test just behaviour.

Anyway, let me know of any issues you find with this.

Thanks for reading and sorry about the syntax non-highlighting.