How to time travel safely, but only on your tests!
During your journey as developer you will need to manipulate time, and by this I mean freezing it, warping to the future or back to the past. We use this when we want to test some code behavior determined by some date. For example, we may need to test some status change of an object when the date is a past one.
As always, there are some gems that help our lives. I believe the most used one is the gem Timecop. In the past Delorean was used as well, but for now I would say it looks a little bit outdated.
Besides those facts, it is valid to argue that the inclusion of another dependency in the system, for the sake of handling a specific function on our tests, may be a bit overkill. Because of this, in the Rails 4.1 update a new helper was added to rescue us: TimeHelpers.
Ok, I know. This version is old. The fact here is that a lot of people have no knowledge that this helper exists. Sometimes it may be necessary to keep the Timecop inside the system, mostly because it has some other functions comparing to the helper, but on my experience, the helper is enough in the majority of cases.
How do I even start?
First, you must be using Rails 4.1 version or higher. Now you must decide if you want to turn this helper available for all the tests or not. If you want to use it only on one test (and this is totally fine), you can do something like this :
But if you think it is a better idea to let this helper available for all your tests, all you have to do is add one line of code to your test configuration. If you are using Rspec it would be something like this:
Features
With this you will have 3 new methods to use: travel
, travel_to
and travel_back
. The first 2 are almost the same thing as
they are used to lock the time. When you use travel
you will need to pass a specific time to be added on top of the current time.
For example:
With travel_to
the time you passes will become the current time and will stay locked on it.
As you can see both methods act almost the same: stubbing the return of some methods in order to lock the time.
Those methods are: Time.now
, Date.today
, and DateTime.now
.
The last method of the helper, travel_back
, is the one responsible to remove all the stubs made before. This way
methods like Time.now
will return the real current time.
Both travel
and travel_to
accept a block as well:
If using a block the time will revert back when the block execution is over. If you are using those methods without a block,
depending on your tests, you may need to call travel_back
to normalize the situation.
Beware!
As stated at the top of this post, there are some differences if we compare the helper and the gem Timecop. The one you must have special attention is the difference when we return time back to normal.
If you are using the helpers in sequence, for example:
What will happen here is that when you call travel_back
the time will return to the real current time, ignoring any other stub you could have made,
even if they were inside a block. We can understand it better if we check the code for the method here:
All the stubs
generated are removed! This will not happen on Timecop for example. The method that does the same on the gem is the return
.
You can check the code for the whole class here.
Having a look at the method:
It is possible to notice that it has 2 different types of action here, where the first part will handle the situation where you have stubbed a previous
Time, enforcing that you will come back to this stub
instead of the real current time. This will determinate if you need to use Timecop or not
on your tests.
Conclusion
If you are cleaning your system or you cannot stand adding unnecessary dependencies to your code (me \o), here is a good example of native Rails feature you can start using today. Just be aware of the differences. I recommend that you take a look at the GitHub page of Timecop and compare it to the helper sou you can be safe and avoid future problems.