Rails testing: Thay đổi thời gian với TimeHelpers

Vấn đề

Hãy tưởng tượng bạn đang test ứng dụng Rails của mình với RSpec, bạn cần di chuyển thời gian đến tương lai hoặc quá khứ để test các chức năng của mình. Bạn biết rằng Timecop có đầy đủ những thứ bạn cần về thời gian nhưng bạn cũng mới nghe về 1 module được xây dựng sẵn từ Rails 4.1 tên ActiveSupport::Testing::TimeHelpers đáp ứng đầy đủ những thứ bạn cần. Bạn sẽ quyết định sẽ dùng gem Timecop hay sử dụng module kia?

Để có câu trả lời cho riêng mình, chúng ta hãy cũng tìm hiểu về module TimeHelpers nhé!

Các method của Timehelpers

travel(duration, &block)

Thay đổi thời gian hiện tại thành thời gian trong tương lai hoặc trong quá khứ bằng khoảng cách đến Time.now, Date.todayDateTime.now

Time.current      # => Tue, 23 Jan 2018 22:13:47 JST +09:00
travel 1.day      
Time.current      # => Wed, 24 Jan 2018 22:14:20 JST +09:00
Date.current      # => Wed, 24 Jan 2018
DateTime.current  # => Wed, 24 Jan 2018 22:14:20 +0900

Phương thức này cũng chấp nhận một block, nó sẽ trả về thời gian ban đầu ở cuối block:

Time.current              # => Wed, 24 Jan 2018 22:14:20 JST +09:00
travel 1.day do
  User.create.created_at  # => Wed, 24 Jan 2018 22:14:44 JST +09:00
end
Time.current              # => Tue, 23 Jan 2018 22:17:28 JST +09:00

travel_back() Trả về thời gian ban đầu bằng cách xóa các phần khai báo được thêm vào bởi traveltravel_to

Time.current   # => Tue, 23 Jan 2018 22:18:10 JST +09:00
travel_to Time.zone.local(2015, 11, 24, 01, 04, 44)
Time.current   # => Tue, 24 Nov 2015 01:04:44 JST +09:00
travel_back
Time.current   # => Tue, 23 Jan 2018 22:19:23 JST +09:00

travel_to(date_or_time) Thay đổi thời gian hiện tại thành thời gian cho trước bằng cách bỏ Time.now, Date.todayDateTime.now để trả lại thời gian hoặc ngày được truyền vào phương thức này.

Time.current     # => Tue, 23 Jan 2018 22:20:01 JST +09:00
travel_to Time.zone.local(2015, 11, 24, 01, 04, 44)
Time.current     # => Tue, 24 Nov 2015 01:04:44 JST +09:00
Date.current     # => Tue, 24 Nov 2015
DateTime.current # => Tue, 24 Nov 2015 01:04:44 +0900

Ngày được lấy làm dấu thời gian là đầu ngày trong múi giờ của ứng dụng. Time.current trả về dấu thời gian, và Time.now tương đương của nó trong múi giờ hệ thống. Tương tự như vậy, Date.current trả về một ngày bằng đối số, và Date.today là ngày theo Time.now. (Lưu ý rằng bạn hiếm khi phải xử lí Time.now, hoặc Date.today, để tôn trọng múi giờ của ứng dụng, hãy luôn sử dụng Time.currentDate.current.)

Phương thức này cũng chấp nhận một block, nó sẽ trả về thời gian ban đầu ở cuối block:

Time.current    # => Tue, 24 Nov 2015 01:04:44 JST +09:00
travel_to Time.zone.local(2015, 11, 24, 01, 04, 44) do
  Time.current  # => Tue, 24 Nov 2015 01:04:44 JST +09:00
end
Time.current    # => Tue, 23 Jan 2018 22:27:42 JST +09:00

Ứng dụng trong Rspec

Tưởng tượng rằng bạn phải thống kê số like của người dùng trong ngày hôm qua, bạn viết RSpec như sau:

require "rails_helper"

describe "#create" do
  before do
    travel_to("2017-01-18 10:00:00") 
    user.like.create
  end
  
  context "when user liked" do
    it "should create stats of today like" do
       travel_to("2017-01-19 10:00:00")
       expect(StatsOfLike.on_yesterday.size).to eq 1
    end
  end
end

Khi bạn chạy test của bạn, bạn nhận đc thông báo lỗi method_missing: undefined method travel_back for RSpec::ExampleGroups. Làm thế nào để sửa lỗi này? Hãy include ActiveSupport::Testing::TimeHelpers vào phần test của bạn.

require "rails_helper"

include ActiveSupport::Testing::TimeHelpers
describe "#create" do
  before do
    travel_to("2017-01-18 10:00:00") 
    user.like.create
  end
  
  context "when user liked" do
    it "should create stats of today like" do
       travel_to("2017-01-19 10:00:00")
       expect(StatsOfLike.on_yesterday.size).to eq 1
    end
  end
end

Một cách khác để thêm module này vào:

RSpec.configure do |config|
  ....
  config.include ActiveSupport::Testing::TimeHelpers
  ....
end

Vậy là mình đã hoàn thành bài giới thiệu về module TimeHelpers, chúc các bạn viết test dễ dàng hơn với module này!