ruby-on-railsrubyfactory-bottimecop

Timecop with Factory_girl: factory not sequencing date attribute as expected


I am using the timecop gem in order to stub out dates. I am also using factory_girl in order to make creating objects for my specs easier.

Here is my factory:

FactoryGirl.define do
  factory :fiscal_year do
    start_date  {Date.today}
    end_date    {Date.today + 1.day}

    trait(:sequenced_start_date) {sequence(:start_date, Date.today.year)  {|n| Date.new(n, 10, 1) }}
    trait(:sequenced_end_date)   {sequence(:end_date,   Date.today.year + 1)  {|n| Date.new(n, 9, 30) }}


    factory :sequenced_fiscal_year, parent: :fiscal_year,
            traits: [:sequenced_start_date, :sequenced_end_date]
  end
end

Assume that today's actual date is 09/07/2016.

Here is my spec:

RSpec.describe FiscalYear, type: :model do
  before(:example) do
    Timecop.freeze(Time.local(2010))
  end
  after do
    Timecop.return
  end

  describe "fake spec to show unexpected sequence behavior" do
    it "tests something" do
      puts "Today's frozen date thanks to Timecop: #{Date.today}"
      fiscal_year = create(:sequenced_fiscal_year)
      puts "Sequenced Date: #{fiscal_year.end_date}"
    end
  end
end

When the spec is run here is the output:

Today's frozen date thanks to Timecop: 01/01/2010
Sequenced Date: 09/30/2017

In the first output line: Timecop works as expected when I call Date.today. It has successfully frozen the time and returns that frozen time.

However: the second line does not work as expected. It is clearly using the system Date as opposed to Timecop's frozen Date. The sequenced end_date should sequentially add +1 year to the currently frozen Date. Therefore: I expect the end_date here to be 09/30/2011, as opposed to 09/30/2017.

How can I make it so that the sequenced end_date attribute goes off of Timecop's Date as opposed to the system's Date?


Solution

  • The issue was with the second argument I was passing into the sequence method. That argument is static/evaluated at initialization. It is not dynamic, so timecop did not have the chance to stub out the date before that second argument is evaluated.

    trait(:sequenced_end_date) {sequence(:end_date, Date.today.year + 1)  {|n| Date.new(n, 9, 30) }}
    

    That Date.today.year part of the second argument being passed into the sequence method is static and evaluated at initialization. It is not dynamic and is already evaluated before the method is even called.

    I simply needed to change those methods to the following:

    trait(:sequenced_year_start_date) {sequence(:start_date)  {|n| Date.new(Date.today.year, 10, 1) }}
    trait(:sequenced_year_end_date)   {sequence(:end_date)    {|n| Date.new(Date.today.year + n, 9, 30) }}
    

    Now it works as expected and returns the following:

    Today's frozen date thanks to Timecop: 01/01/2010
    Sequenced Date: 09/30/2011
    

    Blocks are dynamic, so calling Date.today.year + n within the block gives timecop the chance to first stub out Date before this block is executed.