Stubbing time in a Capybara test - ruby-on-rails

I have a page that creates a snapshot of a document. That document is saved with the title being a timestamp (September 27, 2014 at 4:01:10 pm) for example. I am writing a test for this page and want to stub time so that it doesn't change.
What I have at the moment is Time.stubs(:now).returns(Time.parse("2014-1-2 11:00:00")) but when I do that I get an error message saying:
Capybara::FrozenInTime: time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead
What is the best way to stub out time here?

Rails now includes support for time travel directly, e.g.:
feature 'Time travel verifier' do
include ActiveSupport::Testing::TimeHelpers
scenario 'works in the past' do
travel_to(1.day.ago) do
visit time_travel_verification_path
expect(page).to have_content('WOAH Time Travel!')
end
end
end

I'm reposting here my comment as an answer.
There is the timecop gem https://github.com/travisjeffery/timecop
It allows you to do things like the following:
describe "some set of tests to mock" do
before do
Timecop.freeze(Time.local(1990))
end
after do
Timecop.return
end
it "should do blah blah blah" {}
end
which will make the tests run as if it was 1990-1-1 and then return back to the current time.

Related

Rspec surrounding shared_example with timecop

I am trying to wrap Timecop around a shared_example spec i wrote
describe "timecop wrapping shared example" do
Timecop.freeze(1.day.ago) do
it_behaves_like "a shared example i would like to test using timecop"
end
end
shared_examples "a shared example i would like to test using timecop"
before :all do
puts "the current time is #{Time.now}"
end
... expect ...
end
but running this spec still uses the real time and not the frozen one
can Timecop work like this?
how else can i wrap large pieces of my test file but change the time it is running
Timecop needs to execute within an it or before block.
The following change will fix your problem.
describe "timecop wrapping shared example" do
before { Timecop.freeze(1.day.ago) }
it_behaves_like "a shared example i would like to test using timecop"
end
Rspec will reset the environment(including Timecop) after running each example.
A couple of tips I've found with time travel in tests that might be useful:
Timecop.travel is more reflective of how your code will work in real life as time never stays still. You can use it the same way as freeze, just swap out the freeze method for travel.
If you have Rails 4.1 or newer there are great time travel tools built in and you can drop your timecop gem. Here is a blog post and the docs if you want to learn more.

Time.now returns wrong result in test environment

I'm running an RSpec test suite with capybara in my Rails app.
When I put binding.pry inside of a test, and try Time.current, I get => Tue, 15 Dec 2015 00:06:30 UTC +00:00, which is three months ago. As far as my basic knowledge of geography goes, there isn't a time zone that has a 3 months difference with EST. So what am I missing here? :-)
Running same query in development environment (i.e. through rails console) returns correct result.
It sounds like you have specs in your suite that use the Timecop gem. So, have a look for every instance of Timecop.freeze and ensure there is a corresponding Timecop.return statement in order to do proper teardown of the time freeze and prevent the unexpected side effect of it leaking into other specs in your suite. Here is an example from the gem's README:
describe "some set of tests to mock" do
before do
Timecop.freeze(Time.local(1990))
end
after do
Timecop.return
end
it "should do blah blah blah" do
end
end

Rails RSpec Mocking Date.today.wday in Rake Task

I'm trying to mock Date.today.wday in a rake task in rspec.
Gem versions: RSpec 2.14.8 --- Rails 4.1.1 --- ruby 2.0.0
Here is a simplified fake version of my test to illustrate essentially what I'm trying to do:
describe "scheduler" do
describe ":thursday_invitations" do
let(:run_issue_invites) do
Rake::Task[:thursday_invitations].reenable
Rake.application.invoke_task :thursday_invitations
end
before do
Rake.application.rake_require 'tasks/scheduler'
Rake::Task.define_task(:environment)
Date.today.should_receive(:wday).and_return(4) ###MY NEMESIS CODE LINE
end
context "on thursday" do
it "issues invitations" do
expect(Date.today.wday).to eq(4) ###THE VERIFICATION TEST THAT KEEPS FAILING
run_issue_invites
expect(<other_stuff_to_test>).to <return_properly>
end
end
end
end
So, the real key of this is mocking out the Date.today.wday. Because I want to be able to run my spec on any day of the week, I need to mock/stub out this method to always return "4" (the day-number for Thursday in Rails). So, I initially setup my test to first verify that it is receiving a "4" (the assertion in the test). If today is, say, Friday (which it is) and I run the test, it fails saying that it expected "4" but got "5". That is, it is not returning the value that I want it to when I receive the method. I have tried stubbing with similar ineffective results. Normally, mocking is a breeze, but what seems to be the hangup is .wday which operates on Date.today.
Because this is a rake task (which I'm not as familiar with mocking), I may have to specify something further, but I haven't been able to get to the bottom of it...
Let me know if you need any other clarifying information.
I believe the reason you're not seeing the behavior you expect is that the object you are mocking is the not the same object under test.
In a Rails 4+ environment, this is what I see on the rails console:
[1]> Date.today.object_id
70104549170200
[2]> Date.today.object_id
70104552970360
The fact that the object_id is different in subsequent calls to Date.today means that each call returns a new object. So Date.today.should_receive(:wday).and_return(4) is setting an expectation on an object that will never be used again.
You'll need to rewrite your spec to ensure the same object is returned by Date.today each time. Here's one solution, omitting other parts of your example for clarity:
let!(:today) { Date.today }
before do
Date.stub(:today).and_return(today)
today.should_receive(:wday).and_return(4)
end
it "issues invitations" do
expect(Date.today.wday).to eq(4)
end

Capybara specs with time mocking headache

I have been struggling with a very weird Capybara/Rspec behavior that has something to do with Time.now mocking.
First, I tried to define a set of specs with some models having three date fields set to the value on DateTime.stub(:now) { DateTime.now.utc }. When executed the whole test suite by doing rspec spec the test always failed, however, when doing rspec spec/features/feature all defined specs succedded.
While researching, some people suggested to use Timecop or Delorean gems to fake the current time to avoid having problems with Capybara. I tried both with no luck. I added to the before/after each blocks something like Timecop.travel(time); all suite's specs succeeded this time, but executing the single spec file did yield failing specs .
What would be the best way to garantee deterministic outcomes from specs that deal with time as in Compute my bicycle average speed during the last month/week/day ?
I want to ALWAYS execute all such specs (defined in rspec spec/features/feature) within a given fixed date, let's say June 13, 2010 15:70:00 so that I can do, DateTime.now-1.year and I get June 13, 2009 15:70:00, and the result is always consistent regardless if I am executing it as a separate spec file or as a suite.
Example of failing test:
describe "Stats Spec" do
include IntegrationHelper
before :each do
I18n.locale = :en
DateTime.stub(:now) { DateTime.new(2012,10,11,14,50).utc }
end
context "Given Loui has been cycling during the last year" do
before(:each) do
#loui = Fabricate(:standard_user, username: 'Loui')
stub_track(:today, #loui.id, 3000)
stub_track(:this_week, #loui.id, 5000)
stub_track(:this_month, #loui.id, 8000)
stub_track(:six_months_ago, #loui.id, 3000)
end
specify "he should be able to see his kms and today stats", js: true do
# This spec checks that 3 km appear when clicked on "show today stats" button
end
specify "he should be able to see his kms and week stats", js: true do
# This spec checks that 3+5 km appear when clicked on "show weekly stats" button
end
end
end
def stub_track(time, user_id, meters)
# Fabricates a track using DateTime.now-(3.days/1.week/2.months) with the provided
# user_id and provided meters
end

Capybara Selenium webdriver Transactional Rollbacks work arounds

I'm writing a couple of Feature Specs for an app and using the default Selenium webdriver that comes with Capybara. This is the spec I have written.
DatabaseCleaner.cleaning do
find(:css,'.dropdown-toggle').click
click_on "Locations"
find(:css, "#location-8-upgradesub-60").click
value1 = find(:css, "#location-8-review-subscription").text
value1.should be == '(2) Reviews (Paid)'
end
I'm facing 2 issues with this snippet:
1) Capybara isn't waiting for the XHR to get over and is coming out of the test before that. It works if I give a sleep condition for about 10 sec.
UPDATE
Solved 1) by setting Capybara.default_wait_time = 15 and writing a helper to make sure jQuery isn't active. page.evaluate_script('jQuery.active').zero?
2) I'm not able to rollback the DB transaction that takes place when selenim simulates the test. I see an INSERT and COMMIT in the test.log but no ROLLBACK because of which I need to keep changing my specs every time I run the test. If I use,DatabaseCleaner.strategy = :truncation, my entire DB gets wiped out and that is not something I want.
I've done some extensive googling on this issue and haven't been able to find an efficient work around. I've tried using the same transactional thread too, for the test server. Haven't had fruitful results with too! Any heads up or help would be greatly appreciated. Thanks in advance!
UPDATE
I followed this link https://relishapp.com/rspec/rspec-rails/docs/transactions and put my spec inside a before(:each) block and stored the value in an #value1 instance variable to compare it with the desired value within the it block. I haven't had any luck with that too.
before(:each) do
find(:css,'.dropdown-toggle').click
click_on "Locations"
find(:css, "#location-8-upgradesub-60").click
wait_for_ajax #Helper method to wait for ajax call to get over
find(:css, "#location-8-review-subscription").should be_visible
#value1 = find(:css, "#location-8-review-subscription").text
end
it "should open the dropdown, find Location and open it's modal", js:true do
#value1.should be == '(2) Reviews (Paid)'
end
With 1), I think have_content or have_selector will work. These methods will wait for some seconds before checking the content/selector exist. You could config this time via spec_helper.rb. You could put have_content/have_selector BEFORE your find(..).click to make sure it is exist before next tests.
Finally found a work around. I added this code snippet in spec_helper.rb. Not using Database Cleaner anymore.
Reference: http://www.opinionatedprogrammer.com/2011/02/capybara-and-selenium-with-rspec-and-rails-3/#comment-441060846. The entire comment thread is pretty useful.
ActiveRecord::ConnectionAdapters::ConnectionPool.class_eval do
def current_connection_id
# Thread.current.object_id
Thread.main.object_id
end
end

Resources