Forum: Ruby on Rails Consistent testing with methods that depend on Date.today

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
David G. (Guest)
on 2006-01-18 20:28
(Received via mailing list)
Hi everyone

I'm building an app in which quite a bit of data is dependant on dates
(and times), and I have logic that depends on when 'today' is.  I'm
trying to get some better functional tests written, and am unsure of
the best way to approach it.

The problem is that I want the tests to run against the same data
every time, whether I run it on Tuesday or Thursday or whatever day.
My fixtures are dynamic in that they add data relative to the
beginning of 'this week' (day of the week is important for this data
to make sense, so I can't go relative to Date.today directly).  In one
of my actions, I return data for the 'next three days', and as such,
tests are run against different data depending on what day of the week
the tests are run on.  This is not what I want - tests should get run
against the _same_ data each time (imho).

I know this problem has to have been solved in the past, but I just
can't seem to put my finger on a clean method to run these tests on a
consistent set of data.  Do people mock out the Date.today method to
return a specific date, or what?

Thanks for any hints,
Dave

--
Dave Goodlad
removed_email_address@domain.invalid or removed_email_address@domain.invalid
http://david.goodlad.ca/
Alex Y. (Guest)
on 2006-01-18 20:31
(Received via mailing list)
David G. wrote:
> I know this problem has to have been solved in the past, but I just
> can't seem to put my finger on a clean method to run these tests on a
> consistent set of data.  Do people mock out the Date.today method to
> return a specific date, or what?
I can't say I've had exactly this problem, but that's the first thing
I'd try.  Either that or extract out whatever date-retrieval
functionality you need to a separate model class which uses Date.* in
the actual app, but static data in a mock.
Darragh C. (Guest)
on 2006-01-19 00:53
Alex Y. wrote:
> David G. wrote:
>> I know this problem has to have been solved in the past, but I just
>> can't seem to put my finger on a clean method to run these tests on a
>> consistent set of data.  Do people mock out the Date.today method to
>> return a specific date, or what?
> I can't say I've had exactly this problem, but that's the first thing
> I'd try.  Either that or extract out whatever date-retrieval
> functionality you need to a separate model class which uses Date.* in
> the actual app, but static data in a mock.

I'm sure you can apply my approach the following approach to fit your
problem.

I've a file mutable_time.rb which I include when running my tests.

class << Time
  alias_method :original_now, :now

  def set(time)
    @now = time
  end

  def advance_now_in_seconds(seconds)
    @now ||= now
    @now += seconds
  end

  def now
    @now || original_now
  end

  def use_original_now
    @now = nil
  end
end

I don't use dynamic fixtures - I find it easier to keep the fixtures the
same - and test different scenarios by changing Time.now - that way it's
easier to talk about fixtures when discussing acceptance tests etc.

so if i wanted to test what happens if time is same as the time in my
fixture then I'd call something like

Time.set(fixture(:item).date)

Hope that helps/
David G. (Guest)
on 2006-01-19 01:23
(Received via mailing list)
On 1/18/06, Darragh C. <removed_email_address@domain.invalid> wrote:
>
> I'm sure you can apply my approach the following approach to fit your
> problem.
>
> I've a file mutable_time.rb which I include when running my tests.
>
> class << Time
[snip]
>
> I don't use dynamic fixtures - I find it easier to keep the fixtures the
> same - and test different scenarios by changing Time.now - that way it's
> easier to talk about fixtures when discussing acceptance tests etc.
>
> so if i wanted to test what happens if time is same as the time in my
> fixture then I'd call something like
>
> Time.set(fixture(:item).date)
>

Do you have any problems with any of Rails' built-in functionality
getting confused when you redefine Time#now like that?  After some
experimentation, it seems that the Date class relies on Time#now, so I
could isolate all of my "meddling" to changing one definition and have
it remain consistent throughout the app.  I'm just concerned that I'll
break something subtle in the process!

Dave

> Hope that helps/
>
> --
> Posted via http://www.ruby-forum.com/.
> _______________________________________________
> Rails mailing list
> removed_email_address@domain.invalid
> http://lists.rubyonrails.org/mailman/listinfo/rails
>


--
Dave Goodlad
removed_email_address@domain.invalid or removed_email_address@domain.invalid
http://david.goodlad.ca/
Darragh C. (Guest)
on 2006-01-19 01:35
David G. wrote:

> Do you have any problems with any of Rails' built-in functionality
> getting confused when you redefine Time#now like that?

I typically have a teardown to reset to normal time -

def teardown
  Time.use_system_time
end

only weirdness I've seen is the time report when you run a test can
start saying billions of seconds - but i think that's only when i forget
to have a teardown.
Michael S. (Guest)
on 2006-01-19 02:51
(Received via mailing list)
I had more or less the same problem, and I solved it by overriding
Time::now

My app takes timezones and daylight savings time into account, so I
wanted
to be able to set "today" to days that are or are not in DST.

I'm also using Salted Login Generator, which does it's own (different)
override of Time::now, so I wrote some code that incorporates both
pieces of
logic.

Following is my time.rb, which I use only for testing.  If you don't
care
about the Salted Login Generator stuff, you can ignore
"@@advance_by_days"
related stuff.

To use it, you can call "Time.set_now_gmt(year, month, day, hour,
minute,
second)", and then call "Time.clear_now_gmt".  Better, you can pass a
block
into set_now_gmt, and the change will only affect that block of code.


require 'time'

class Time
  @@advance_by_days = 0
  cattr_accessor :advance_by_days


  #MES- This is some super-funky stuff to test times and timezones.  To
  # properly test timezones, we need to test at times that are affected
by
  # daylight savings time (e.g. to see that DST doesn't screw up our
  # local to UTC conversions, etc.)  But we can't count on tests being
run
  # on days that are near a DST switchover, so we need to fake out the
test-
  # to pretend that we're running at one of those times.  These
functions
  # let the caller set what is considered "now" by the Time class, which
cascades
  # to other Time functions.
  @@now_arr = nil

  def self.set_now_gmt(y, m = 1, d = 1, h = 0, mi = 0, s = 0)
    @@now_arr = [y, m, d, h, mi, s]
    if block_given?
      begin
        yield
      ensure
        clear_now_gmt
      end
    end
  end

  def self.clear_now_gmt
    @@now_arr = nil
  end

  def self.set_advance_by_days(days)
    @@advance_by_days = days
    if block_given?
      begin
        yield
      ensure
        @@advance_by_days = 0
      end
    end
  end


  #MES- OK, this is kinda complicated.  I wrote code to set what's
considered
  # 'now' (see the comment above.)  However, the Salted Login test code
modifies
  # 'now' in a different manner- they use an offset, whereas my code
sets an
  # absolute time.  I've merged these approaches into the 'now' method.
If
there's
  # an offset, it's used.  If there's no offset, but there's an absolute
date, it's used.
  # If neither tweak is set up, the current time is used.
  def self.now
    if Time.advance_by_days != 0
      return Time.at(new.to_i + Time.advance_by_days * 60 * 60 * 24 + 1)
    elsif !@@now_arr.nil?
      self.utc @@now_arr[0], @@now_arr[1], @@now_arr[2], @@now_arr[3],
@@now_arr[4], @@now_arr[5]
    else
      new
    end
  end

end
Scott W. (Guest)
on 2006-01-19 05:22
(Received via mailing list)
I've had good luck isolating the date-sensitive code and making 'now'
a parameter. You can also parameter-ize time-outs and such, and set
them to short time intervals. Of course, these approaches won't work
for some scenarios. It's one of the toughest test nuts to crack.

There was a good discussion of alternatives in one of the XP books, I
think, but just can't find it.

Scott
This topic is locked and can not be replied to.