Manipulating time in Ruby testsEdit
There are lots of possibilities discussed in this question on Stack Overflow, "Ruby unit testing: how to fake Time.now?", but basically the answers boil down to:
- stub
Time.nowmanually using your existing test double framework (RR or whatever) - use a specialized time-stubbing library which provides convenience methods for stubbing
Time.now; examples include:- Delorean: https://github.com/bebanjo/delorean
- Timecop: https://github.com/jtrupiano/timecop
- time_warp: https://github.com/harvesthq/time-warp
- roll your own time-stubbing library
- set up your test fixtures or factories to create objects with the desired timestamps already in place
- don’t stub; instead refactor your code so that you can pass in the "clock" that will be queried for what the time is
Comparison of stubbing libraries
Personally I’ve generally tried to eschew stubbing of a core class like Time and have tried to use the "factory" approach. This, however, can get a bit tiresome, depending on how much time-sensitive specs you have to write.
Delorean
Is a simple library whose implementation consists of a single file about 50 lines long.
It provides the following "cute" API (cute because of the homage to the "Back to the Future" films):
Delorean.time_travel_totaking a string that will be parsed using theChroniclibrary (eg. "2 seconds ago"), or aDateorTimeinstanceDelorean.jumptaking an offset in seconds to move forward or back in timeDelorean.back_to_the_present
The #time_travel_to method takes an optional block that can be used to limit the scope of the stubbing to that block only.
Timecop
This is arguably the most complex and comprehensive of the stubbing libraries, stubbing not only Time.now but also Date.today and DateTime.now (if and only if Date and DateTime have been made available via a require 'date' beforehand).
The following API methods are provided:
Timecop.freezestubsTime.now(et al) to always return a fixed (non-advancing) valueTimecop.traveljumps to the specified timeTimecop.returnrestoresTime.now(et al) to their unmodified behavior
Both #freeze and #travel take an optional block that can be used to limit the scope of the stubbing to that block only.
Both take a parameter which may be:
- a
Time,DateTimeorDateinstance - a numeric offset in seconds to move forwards (or backwards) in time
- a year, month, day, hour, minute, second tuple (all values are optional, defaulting to 0 if not present)
time_warp
This is probably the simplest of the three, having only one method in the API, pretend_now_is, which takes a block and twiddles with the time for the duration of the block.
Overall evaluation
- Delorean:
- pros: simple implementation
- cons: dependency on Chronic gem
- Timecop:
- pros:
- shorter method names
- no redundancy in the API design (ie. the
#travel method encompasses the functionality of both the #time_travel_to and #jump methods in Delorean)
- nice coherence in the API design (ie. both
#freeze and #travel take the same kinds of parameters and optional blocks)
- more features (eg.
#freeze method)
- more complete coverage of time sources (ie. of
Date.today and DateTime.now in addition to Time.now)
- cons: more complex implementation
- time_warp:
- pros: *sound of crickets*
- cons: extremely limited feature set
- pros: simple implementation
- cons: dependency on Chronic gem
- pros:
- shorter method names
- no redundancy in the API design (ie. the
#travelmethod encompasses the functionality of both the#time_travel_toand#jumpmethods in Delorean) - nice coherence in the API design (ie. both
#freezeand#traveltake the same kinds of parameters and optional blocks) - more features (eg.
#freezemethod) - more complete coverage of time sources (ie. of
Date.todayandDateTime.nowin addition toTime.now)
- cons: more complex implementation
- pros: *sound of crickets*
- cons: extremely limited feature set
While I like the simplicity of the Delorean implementation, I think Timecop has a considerably better API. Despite being more complex, the codebase is still small enough to review in a few minutes and to me it looks to be bug free, so my recommendation for now is to go with Timecop.