kirk bushell | Riding the wave of programmatic novelty

An Rspec Model Example

November 12, 2008 at 04:12 PM · Posted under Blog · Tagged with , , ,

Before we get started, I'd just like to put a little disclaimer - this test will be done with database connectivity, meaning we will be hitting the database for any of the methods that will require it. This will ensure not only that our model works completely, but will also highlight any database issues we might have. Let's begin.

For argument's sake, let's just say that our Vehicle table will contain a unique identifier, the name of the vehicle, a manufacturer's id, and a license plate. (id, name, manufacturer_id, license). This is what we'll use as the base fields for our tests.

As stated previously, we're going to setup a model that will highlight the data one might find attributed to a vehicle. To ensure this article encapsulates a lot of what you would usually test in a model, we're also going to convolute it a little with silly extensions like a Doors association, just to stress a few points. Let's look at our base model.

class Vehicle < ActiveRecord::Base
  belongs_to :manufacturer
  has_many :doors, :wheels
  
  def self.vehicles_without_manufacturer
    Vehicle.find(:all, :conditions => {:manufacturer_id => nil} )
  end
end

Here we can see that we have Vehicle model, some basic associations and a custom method, which will retrieve all vehicles that do not have a set manufacturer. In the real world, obviously this wouldn't be the case, but we'll use this as a way of retrieving certain records. Let's look at the beginning RSpec tests.


describe Vehicle do
  
  describe :vehicles_without_manufacturer
    fixtures :all

    before :each do
      @vehicle1 = Vehicle.create!( {:name => 'test vehicle 1', :license => 'abcdef'} )
      @vehicle2 = Vehicle.create!( {:name => 'test vehicle 2', :license => 'ghijkl'} )
      @vehicle3 = Vehicle.create!( {:name => 'test vehicle 2', :license => 'ghijkl', :manufacturer_id => 1} )

      @vehicles = Vehicle.vehicles_without_manufacturer
    end

    it "should have only 2 vehicles that match the requirement" do
      vehicles.length.should == 2
    end
    
    it "should only contain the vehicles that have no manufacturer_id" do
      vehicles.should == [@vehicle1, @vehicle2]
    end

  end

end

Let's go over exactly what is going on here. First and foremost, we're grabbing all our fixtures for our project (these are generally located in test/fixtures, or spec/fixtures for rspec), and usually consists of an empty yml file, with the naming convention of [table].yml (where [table] is the name of the model representing your data). This ensures that each test we run, all data is cleared for that particular model, meaning we're starting from a clean slate each and every time.

Next, we're setting up a way for us to pre-populate the database before each test. More than anything, this makes our tests easier to read, with each individual assert done as a separate test (in this example, ensuring our vehicle length is what it should be and that we're only fetching the matching vehicles).

Now at this point you're probably wondering "well, you're not really testing application-specific stuff, these are things already tested by ActiveRecord's own tests", and you'd be right. What we're really testing here, is to ensure that our database and associated data is setup correctly. Let's move onto something a little more application-specific.

Let's add to our model the method below, which will give us the ability to assign the vehicle to a specified manufacturer, if it's not already assigned.

def add_to_manufacturer(manufacturer)
  if self.manufacturer_id.nil?
    self.manufacturer = manufacturer
  end
end
As you can see, we're only going to set the manufacturer if this particular vehicle record doesn't already have one assigned to it. Pretty straight-forward, right? Let's look at the test. What we're really looking to do, is mimic both conditions - one where a manufacturer_id is already set, and another where the condition is passed and we set the manufacturer, thereby covering all bases.
describe :add_to_manufacturer
  fixtures :all
  
  before :each do
    @vehicle1 = Vehicle.create!( {:name => 'test vehicle 1', :license => 'abcdef'} )
    @vehicle2 = Vehicle.create!( {:name => 'test vehicle 2', :license => 'ghijkl', :manufacturer_id => 2} )
  end
  
  it "should add the manufacturer if it doesn't already exist" do
    @vehicle1.add_to_manufacturer(Manufacturer.create!({field data}))
    @vehicle1.manufacturer.should_not be_nil
  end

  it "should add the manufacturer if it doesn't already exist" do
    @vehicle2.add_to_manufacturer(Manufacturer.create!({field data}))
    updated_vehicle = Vehicle.find_by_id(@vehicle2.id)
    updated_vehicle.manufacturer.should == @vehicle2.manufacturer
  end
end

The main aspect to point out here in our test addition, is the second "should". Notably, we're ensuring that no changes are made to @vehicle2, because if it has changed - then we know that our code check has failed, and we need to have another look at our code.

I hope this helps those of you looking for model examples - let me know if this was of any help, and if not - send me an e-slap and I'll write up another!

Comments

Leave a Comment