Rubocop::Temporal
Is your test suite suddenly failing randomly? Do you use travel_to without blocks? The Department of Temporal Investigations is here to stop help you.
TLDR: This cop checks for calls to travel_to without a block. This is dangerous because a test where this happen might break other tests subtely because now the code runs either in the future or in the past. Using a block will protect you, because after running the block, you will be returned to real time.
The problem
So what is the problem? Let's explore this a bit.
Imagine you have the following spec:
describe 'Enable discounts on New Years Eve' do
context 'when it is new years' do
it 'shows fireworks' do
travel_to '2021-12-31'
expect(product.price).to eq 100 * 0.5
end
end
endLooks good, ship it!
Oh no, random failures:
# Mail is not enqueued suddenly, but only sometimes. When you run this spec individually, it is always sent
describe 'Special member invite' do
it 'invites people who spent at least €1000 to become special members' do
10.times { user.purchase(create(:product, price: 100)) }
expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.with('UserMailer', 'special_member_invite', 'deliver_now', user)
end
end
# This also passes individually, but sometimes we see:
# Luke: Noooooooooooooo
# Darth: No. I am your father
describe 'Message view' do
it 'displays messages from old to new' do
travel_to 2.days.ago
darth_message = send_message(to: 'Luke', 'No. I am your father')
travel_back
luke_message = send_message(to: 'Darth', 'Noooooooooooooo')
expect(page).to have_content(<~~MESSAGE)
Darth: No. I am your father
Luke: Noooooooooooooo
MESSAGE
end
endWhy?
Many 🕐🕑🕒🕓 hours, later 🧽.
It turns out, whenever the Enable discounts on New Years Eve spec runs, it sets the date to the end of this year, but we never return to the current date. The random fails then only happen when the NYE spec runs before them, hence 'random'.
The spec Special member invite fails because on New Years Eve all prices are cut in half, so the user never reaches the treshold of €1000.
The spec Message view fails because when we did travel_to 2.days.ago we went to the 29th of December, not 2.days.ago from the current date. Then when we do travel_back we go back to the current date, lets say the 15th of May and send the second message, or so we think. In reality it is now the first message.
Side note: this does not randomly fail after the 31st of December 2021.
The solution? Passing a block to travel_to. After the block runs, you will be returned to real time.
Installation
Add this line to your application's Gemfile:
gem 'rubocop-temporal', require: falseAnd then execute:
$ bundle install
Or install it yourself as:
$ gem install rubocop-temporal
Usage
Add the following to your .rubocop.yml config:
require:
- rubocop-temporal
If require: was already there, just add - rubocop-temporal nested underneath it.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/abuisman/rubocop-temporal.
License
The gem is available as open source under the terms of the MIT License.