Search code examples
ruby-on-railsrspecrspec-rails

How do I test complex relationships in Rails with RSpec?


Firstly, this question may stray into opinion but I think it's a valuable question to ask. I will give a very specific example for my application which handles absence management and tracking.

An Account has many Users and a User has many Absences. The Account can create PublicHolidays which should be ignored when calculating the number of days that an Absence uses.

Example: If a person takes a week off, the days used will be 5. If one of those days is a PublicHoliday, the days used would be 4.

I want to implement a method such that when a PublicHoliday is created, the days used for any Absences created prior to the date of creation and which cross the date of the PublicHoliday are recalculated.

My current RSpec test looks like this:

it 'triggers a recalculation of absence days on create for absences created before the date of creation of the public holiday' do
  robin = FactoryGirl.create(:robin)
  absence = FactoryGirl.create(:basic_absence, user: robin)
  expect(absence.days_used).to eq(1)
  ph = FactoryGirl.create(:public_holiday, country: "England", account: robin.account)
  expect(absence.reload.days_used).to eq(0)
end

In this test, ph is the same date as the absence so I expect it to calculate one day to start with and then I intend to use an after create callback to recalculate the days used.

Is this the right way to do this test? Is there a more efficient way without creating a number of associated objects?


Solution

  • Firstly - it's good practice to use lets instead of local variables, and secondly - split your tests so each test tests just one thing. Thirdly: anything that sets up a context for tests should be put into a context-block (even if there's only one test in that context)

    eg, here's a re-writing of your spec the standard way:

    let(:robin) { FactoryGirl.create(:robin) }
    let(:absence) { FactoryGirl.create(:basic_absence, user: robin) }
    
    
    context "with no public holidays" do
      it 'counts the absence day to be a day used' do
        expect(absence.days_used).to eq(1)
      end
    end
    
    context "with a public holiday for the absence" do
      before do
        FactoryGirl.create(:public_holiday, country: "England", account: robin.account)
      end
      it 'does not consider the absence day to be a day used' do
        expect(absence.days_used).to eq(0)
      end
    end