Introduction to RSpec - Part 2 - Testing the Rails Model

August 18th, 2008  | Tags:

This is the second part of the post about Introduction to RSpec. You can check the first post HERE.

In this post, we are going to talk about Spec::Rails project, as well as give a simple example of testing a Rails Model.

Installing Spec::Rails as a project plugin

Spec::Rails should be installed as a project plugin, hence you need to create the project itself first.

Let’s create a simple Rails application called rspec_example. Type the following: rails rspec_example. Also, edit the config/database.yml to setup your database (make sure to edit the development and test environament).

After the project is created and setup, it is time to install the Spec::Rails plugin. In the root of the rails project (in my case rspec_test directory) type the following commands (it can take few minutes):

cd vendor/plugins
git clone git://github.com/dchelimsky/rspec.git
git clone git://github.com/dchelimsky/rspec-rails.git
cd rspec
git checkout 1.1.4
cd ../rspec-rails
git checkout 1.1.4
cd ..
rm -rf rspec/.git
rm -rf rspec-rails/.git
cd ../../
script/generate rspec

After that, you will see a directory spec on the root of the project. Also, Spec::Rails brings some generate commands, such as: rspec_model, rspec_controller and so on. Let’s use them.

Installing RCOV

RCov is a code coverage tool for Ruby. It is commonly used for viewing overall test coverage of target code.

To install it, simply type the following: sudo gem install rcov.

Application requirement

For our simple example, let’s create a save funcionality of an User. The user table has 3 fields: username, password and name. All of them are required and the username cannot repeat.

On the user interface, the user must enter the password twice. Both entries must be equals, otherwise an error message must be displayed.

If everything is ok, then the new user can be inserted into the database and a success screen must appear.

Testing the Model

Let’s get started testing our model layer, however we need to create it before. RSpec brings some generates scripts, similar the default from Rails. Hence to create our model, let’s type the following:

ruby script/generate rspec_model User username:string password:string name:string

Look at the generate name: rspec_model. but why should we use this generate instead the tradicional model? Simple, because this generate already create the stub for the rspec test within the rspec/models directory. Before change the rspec script, let’s run the rake to create the model into the database. Run the following:

rake db:migrate

Now, let’s open the spec/models/user_spec.rb. The default code looks like:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper' )
 
describe User do
  before(:each) do
    @user = User.new
  end
 
  it "should be valid" do
    @user.should be_valid
  end
end

To run this code, simple type: rake spec:rcov.

Finished in 0.105384 seconds

1 example, 0 failures

As you can see, the test was performed successfully. However our requirement is not implement. Before implement it (on the model layer), let’s create the test code.

Open again the user_spec.rb file and insert the following content:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper' )
 
describe User,'- the save funcionatlity should be tested according to the requirement' do
 
  fixtures :users
 
  it 'if #username, #password and #name are assigned, the User should be valid' do
    @user = create_user({:username => "jair", :password => "123test", :name => "This is a test"})
    @user.should be_valid
  end
 
  it 'if #username or #password or #name are missing, the User should NOT be valid' do
    #missing username
    user = create_user(missing_username)
    user.should_not be_valid
    #missing password
    user = create_user(missing_password)
    user.should_not be_valid
    #missing name
    user = create_user(missing_name)
    user.should_not be_valid
  end
 
  it 'if ANY attribute is missing in the save(), a message "is required" should be returned' do
    #missing username
    @user = create_user(missing_username)
    @user.save
    @user.errors.on(:username).should be_eql("username is required" )
  end
 
  it 'if a #username already exists, an error "username already exist" should be displayed on save()' do
    user = create_user(all_attributes)
    user.save
    user.errors.on(:username).should be_eql("username already exist" )
  end
 
  private
  def create_user(hash)
    User.new(hash)
  end
  def all_attributes
    hash = {:username => "jairrillo",:password => "test123", :name => "Jair Rillo"}
  end
  def missing_username
    hash = {:password => "test123", :name => "Jair Rillo Junior"}
  end
  def missing_password
    hash = {:username => "jairrillo",:name => "Jair Rillo Junior"}
  end
  def missing_name
    hash = {:username => "jairrillo",:password => "test123"}
  end
end

The code above try to test the model according to the requirement. If we run the rake spec:rcov, you will get 3 errors:

1)
‘User - the save funcionatlity should be tested according to the requirement if a #username already exists, an error “username already exist” should be displayed on save()’ FAILED
expected eql?(”username already exist”) to return true, got false
./spec/models/user_spec.rb:34:

2)
‘User - the save funcionatlity should be tested according to the requirement if ANY attribute is missing in the save(), a message “is required” should be returned’ FAILED
expected eql?(”username is required”) to return true, got false
./spec/models/user_spec.rb:28:

3)
‘User - the save funcionatlity should be tested according to the requirement if #username or #password or #name are missing, the User should NOT be valid’ FAILED
expected valid? to return false, got true
./spec/models/user_spec.rb:15:

Finished in 0.229577 seconds

4 examples, 3 failures

Let’s skip the first error, we will cover it late.

The second and third errors can be fixed together. According to the requirement, none field can be empty. So let’s implement it into the model. Open the app/model/user.rb and add the following:

class User < ActiveRecord::Base
  validates_presence_of :username, :message => "username is required"
  validates_presence_of :password, :message => "password is required"
  validates_presence_of :name,      :message => "name is required"
end

Run the rake spec:rcov again and you will see the following:

1)
‘User - the save funcionatlity should be tested according to the requirement if a #username already exists, an error “username already exist” should be displayed on save()’ FAILED
expected eql?(”username already exist”) to return true, got false
./spec/models/user_spec.rb:34:

Finished in 0.19481 seconds

4 examples, 1 failure

It is getting better, we have now only one error (two were fixed).

To fix this, we are going to use the fixtures feature. Actually, we already setup the fixtures in 5, but we do not change the fixtures file. To do that, open the file spec/fixtures/users.yml. There you can insert some pre-defined values. Below following the example:

# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
 
one:
  username: jairrillo
  password: test123
  name: Jair Rillo Junior
 
two:
  username: test
  password: 123test
  name: Just a test

Now, we need to edit again the app/models/user.rb file and add the following line:

    validates_uniqueness_of :username, :message => "username already exist"

After that run the rake spec:rcov again and…

Finished in 0.448439 seconds

4 examples, 0 failures

All test were performed successfully into the model layer.

There are other interesting tips about RSpec and RCov. For example, after you run the rake spec:rcov, go to the directory coverage and open the index.html through our web-browser. You will see anything like this:

Good result, right?

Also, you can type in the console: rake stats.

Look at the Test Ratio. To keep a average, try to keep the Test Ratio greater than 1:3.0.

I also advice you to check out the official documentation and the mail-list of the RSpec::Rails team. There you will find new commands and ways to test your model application.

This post ends up here. In the next topic we are going to learn how to test the Rails Controller using the same application requirement.

No comments yet.
TOP