Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
3.6k views
in Technique[技术] by (71.8m points)

ruby on rails - How to fix RSpec test that depends on time passing by

I have the following method in one of my controlllers:

def ping
  @obj.last_ping_at = DateTime.now
  @obj.save!
end

I would like to test that this is working, so I wrote the following test:

it 'successfully updates last_ping_at' do
  old_last_ping_at = obj.last_ping_at
  patch url(obj)
  expect(response).to have_http_status(:ok)
  expect(obj.reload.last_ping_at).not_to eql(old_last_ping_at)
end

Here is the problem: the time IS always the same. The only way it changes it's if I add a binding.pry to the controller method and then continue.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Set last_ping_at to something definitely not now, then check if it's close to now with be_within.

it 'successfully updates last_ping_at' do
  obj.update!(last_ping_at: 1.day.ago)

  patch url(obj)

  expect(response).to have_http_status(:ok)
  expect(obj.reload.last_ping_at).to be_within(1.second).of(DateTime.now)
end

Note: DateTime.now will be the time zone on your machine, not the application's time zone. Use DateTime.current instead.

[4] pry(main)> Time.zone = "Fiji"
=> "Fiji"
[5] pry(main)> DateTime.now
=> Thu, 21 Jan 2021 12:13:14 -0800
[6] pry(main)> DateTime.current
=> Fri, 22 Jan 2021 08:13:16 +1200
[7] pry(main)> 

See It's About Time (Zones).


Sometimes you can't use be_within. In that case you can freeze time. For general Ruby use timecop as @max says. Rails has that built in with travel_to.

it 'successfully updates last_ping_at' do
  travel_to Time.current do
    patch url(obj)

    expect(response).to have_http_status(:ok)
    expect(obj.reload.last_ping_at).to eq Time.current
  end
end

I go a step further and bake this into rspec metadata.

# spec/support/config/travel_to.rb
RSpec.configure do |config|
  config.around do |example|
    if example.metadata[:travel_to]
      travel_to example.metadata[:travel_to] do
        example.run
      end
    else
      example.run
    end
  end
end

Now you can travel_to: Time.current in any RSpec block.

it 'successfully updates last_ping_at', travel_to: Time.current do
  patch url(obj)

  expect(response).to have_http_status(:ok)
  expect(obj.reload.last_ping_at).to eq Time.current
end

YMMV whether you prefer the explicit block or the implicit metadata.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...