4

Am swinging between Shoulda and Rspec these days. I have read and played around a fair bit with RSpec but not that much with Shoulda. I find Shoulda's one line assertions easier to read and the test looks cleaner. But when I can't figure out how write a particular assertion in Shoulda I switch to RSpec. Not very happy about it though.

So here is what I did today. I wrote some custom validations for my model Course. A course has a start_date and an end_date. There are a few rules around it.

  • start_date and end_date are both mandatory
  • start_date cannot be later than today
  • end_date cannot be before the start_date

I know there are quiet a few gems out there that could have done it for me. But coz I am new I thought it might be good idea to do it myself and learn as I go.

So this is what my model looks like

class Course < ActiveRecord::Base
  belongs_to :category
  has_many :batches, :dependent => :destroy
  accepts_nested_attributes_for :batches, :reject_if => lambda {|a| a[:code].blank?}, :allow_destroy => true
  has_and_belongs_to_many :students, :uniq => true

  validates_presence_of :name, :course_code, :total_seats
  validates_uniqueness_of :category_id, :scope => [:name, :course_code]

  validates :start_date, :presence => true, :course_start_date=>true
  validates :end_date, :presence => true, :course_end_date=>true
end

My custom validations are as follows

class CourseEndDateValidator < ActiveModel::EachValidator  
  def validate_each(object, attribute, value)
    if object.errors[attribute].blank? && object.errors[:start_date].blank?
      if value < object.start_date
        object.errors[attribute] << "cannot be later than start date"
      end
    end
  end
end

class CourseStartDateValidator < ActiveModel::EachValidator  
  def validate_each(object, attribute, value)
    if object.errors[attribute].blank?
      if value < DateTime.now.to_date
        object.errors[attribute] << "cannot be later than today"
      end
    end
  end
end

And following is my course_spec

require 'spec_helper'require 'date'

describe Course do

  context  'validations' do
    it { should validate_presence_of(:name)}
    it { should validate_presence_of(:course_code)}
    it { should validate_presence_of(:start_date)}
    it { should validate_presence_of(:end_date)}
    it { should validate_presence_of(:total_seats)}

    date = DateTime.now.to_date
    it { should allow_value(date).for(:start_date) }
    it { should_not allow_value(date - 10 ).for(:start_date) }
    it {should allow_value(date + 10).for(:end_date)}
  end

  context  'associations' do
    it { should belong_to(:category)}
    it { should have_many(:batches).dependent(:destroy)}
    it { should have_and_belong_to_many(:students) }
  end

  it " end date should not be before course start date" do
    course = FactoryGirl.build(:course, :end_date=>'2011-12-10')
    course.should be_invalid
  end
end

Now before I wrote the last "it" block using Rspec I had something like this in my validations context

context  'validations' do
    it { should validate_presence_of(:name)}
    it { should validate_presence_of(:course_code)}
    it { should validate_presence_of(:start_date)}
    it { should validate_presence_of(:end_date)}
    it { should validate_presence_of(:total_seats)}

    date = DateTime.now.to_date
    it { should allow_value(date).for(:start_date) }
    it { should_not allow_value(date - 10 ).for(:start_date) }
    it { should allow_value(date + 10).for(:end_date)}
    it { should_not allow_value(date - 10).for(:end_date)} # <-------------------
  end

And I got the following failure

Failures:

  1) Course validations
     Failure/Error: it { should_not allow_value(date - 10).for(:end_date)}
       Expected errors when end_date is set to Fri, 9 Dec 2011, got errors: ["name can't be blank (nil)", "course_code can't be blank (nil)", "total_seats can't be blank (nil)", "start_date can't be blank (nil)"]

Am not sure what am I doing wrong here. Is it my custom validation code that is not correct or I need to setup something before I run the last assertion so that start_date is not nil when testing end_date?

The validations work fine in the view. I mean I get the right validation errors depending on the kind of data I input. But am test is failing. I have been looking at this for a while now but cannot figure out what exactly am I doing wrong.

MMinhas
  • 73
  • 2
  • 8

2 Answers2

3

I think you could tackle this in one of two two ways:

Either you need to place you date = DateTime.now.to_date into to before(:each) block.

context  'validations' do
  before(:each) { date = DateTime.now.to_date }

  it { should allow_value(date).for(:start_date) }
  it { should_not allow_value(date - 10 ).for(:start_date) }
  it { should allow_value(date + 10).for(:end_date)}
  it { should_not allow_value(date - 10).for(:end_date)}
end

Or you could use the rails date helpers.

context  'validations' do
  it { should allow_value(Date.today).for(:start_date) }
  it { should_not allow_value(10.days.ago).for(:start_date) }
  it { should allow_value(10.days.from_now).for(:end_date)}
  it { should_not allow_value(10.days.ago).for(:end_date)}
end
nmott
  • 9,454
  • 3
  • 45
  • 34
0

@nickgrim already answered the question, but I want to add a comment. The point of describe and it is to encourage sentences that start with the words "describe" and "it". In your example, you've got this:

it " end date should not be before course start date" do
  # ...

"it end date ...." is not a sentence. Please write that as something like:

it "validates that end date should be >= start date" do
David Chelimsky
  • 8,920
  • 2
  • 38
  • 30
  • Thanks for the input but I thought when the steps are printed out on console they should make readable sentences. so my output on console when I run my spec is something like this `Course end date should not be before course start date validations should require name to be set should require course_code to be set ... ` What you are suggesting is correct for someone reading the spec. Am not sure which approach to follow now. Any thoughts? – MMinhas Dec 19 '11 at 22:57
  • From my perspective, we should work to make the spec and the console output readable. But if you can't have both (kind see why) the output of the console is way more important, if you use the output in verbose mode. If you only use the dots, then the spec is more important. But I definitely like to see a good readable console output, specially when the tests fail :) – Victor Martins Oct 22 '13 at 15:41