Wednesday, April 23, 2008

what to do if autotest does not autotesting

Just spent few hours of my life trying to find out why eventually autotest stopped running specs for the files changed. There was no exceptions, nothing weird, it was just not picking up changes anymore. I've updated ZenTest, then updated rspec and rspec_rails, then switched these to edge, nothing helped.
After all I've checked my disk with `fsck -n` and there were bunch of errors there.
`shutdown -rF` effectively returned autotest to life, left me thinking of how important this simple tool is for me and my job.
Hope this will save somebody some time.

Monday, February 25, 2008

rspec aids refactoring

Idea of defining specs dynamically, like
[:show, :edit, :about, :password].each do |action|
describe "#{action}" do
it "should be successful" do
get action
response.should be_success
end

it "should render accounts/#{action}" do
get action
response.should render_template("accounts/#{action}")
end
end
end

is not new, but only now, after reading this post on metaprogramming, I realized relation between dynamically generated specs and the code under these specs.

In most cases, dynamically generated specs indicates opportunity to dynamically generate code under specs.

And vice versa.

Sunday, February 24, 2008

Way too much method stubs in controller specs?

Just a quick tip.
If you feel overstubbing
@visitor.stub!(:last_seen_at=)
@visitor.stub!(:update_attributes!)

Move more stuff to models
@visitor.stub!(:update_last_seen_time!)

Nested example groups in controller specs

If I've been missing something in rspec badly, it was ability to nest my describe blocks in a reasonable way. Now it's there - starting from version 1.1.0 you may nest your specs, if you still not doing so.

describe "something" do
it "should do something"
describe "with something else" do
it "should do even more"
end
end


Even shared behaviors may be nested, visible within containing block only, but this feature is not yet released, so you'll have to switch your rspec copys to trunk, if you really need it.

Today I started refactoring controller specs to bring more structure into them. Like this:

describe AccountsController do
before(:each) do
@account = mock_model(Person)
@account.stub!(:logged_in?).and_return(true)
Person.stub!(:find_by_id).and_return(@account)
end

it "should have get /account mapped to :show" do
params_from(:get, "/account").should == {:controller => "accounts", :action => "show"}
end

it ":show should be mapped back to /account " do
route_for(:controller => "accounts", :action => "show").should == "/account"
end

it "should have get /account/edit mapped to :edit" do
params_from(:get, "/account/edit").should == {:controller => "accounts", :action => "edit"}
end

it ":edit should be mapped back to /account/edit" do
route_for(:controller => "accounts", :action => "edit").should == "/account/edit"
end

it "should have put /account mapped to :update" do
params_from(:put, "/account").should == {:controller => "accounts", :action => "update"}
end

it ":update should be mapped back to /account" do
route_for(:controller => "accounts", :action => "update").should == "/account"
end

describe "show" do
it "response should be success" do
get :show
response.should be_success
end

it "should render accounts/show" do
get :show
response.should render_template('accounts/show')
end
end

describe "edit" do
it "response should be success" do
get :edit
response.should be_success
end

it "should render accounts/edit" do
get :edit
response.should render_template('accounts/edit')
end
end

describe "successful update" do
it "response should be redirect" do
@account.should_receive(:update_attributes).and_return(true)
put :update
response.should be_redirect
end
end

describe "failed update" do
def do_failed_update(params = {})
@account.should_receive(:update_attributes).and_return(false)
put :update, params
end

it "response should be unprocessable entity" do
do_failed_update :account => { :full_name => 'Bruno', :email => 'nothing@to.com' }
response.headers["Status"].should == "422 Unprocessable Entity"
end

it "should render template accounts/edit when account info update failed" do
do_failed_update :account => { :full_name => 'Bruno', :email => 'nothing@to.com' }
response.should render_template('accounts/edit')
end

it "should render template accounts/password when password update failed" do
do_failed_update :account => { :password => 'mipass' }
response.should render_template('accounts/password')
end
end
end


Results looks great with specdoc formatter


AccountsController failed update
- should render template accounts/password when password update failed
- should render template accounts/edit when account info update failed
- response should be unprocessable entity

AccountsController successful update
- response should be redirect

AccountsController edit
- should render template accounts/edit
- response should be success

AccountsController show
- should render template accounts/show
- response should be success

AccountsController
- :update should be mapped back to /account
- should have put /account mapped to :update
- :edit should be mapped back to /account/edit
- should have get /account/edit mapped to :edit
- :show should be mapped back to /account
- should have get /account mapped to :show


Surely, you'll have to spec against other visitor roles, but with one shared behaviour, few helpers and a bit of luck that would not be the problem.