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 email@example.com or firstname.lastname@example.org http://david.goodlad.ca/
on 2006-01-18 19:28
on 2006-01-18 19:31
David Goodlad 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.
on 2006-01-18 23:53
Alex Young wrote: > David Goodlad 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/
on 2006-01-19 00:23
On 1/18/06, Darragh Curran <email@example.com> 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 > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails > -- Dave Goodlad firstname.lastname@example.org or email@example.com http://david.goodlad.ca/
on 2006-01-19 00:35
David Goodlad 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.
on 2006-01-19 01:51
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, @@now_arr, @@now_arr, @@now_arr, @@now_arr, @@now_arr else new end end end
on 2006-01-19 04:22
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