ActiveSupport::TestCase中可以使用travel_to指定测试日期,令block中的代码的“当前时间”变成指定日期

class GoodPriceTest < ActiveSupport::TestCase
  test 'xxxxx' do
    travel_to Time.zone.parse('2017-12-16 15:19:39') do
      # assert_equal ...
    end
  end
end


其源码如下。主要是给Time、Date重定义now、today方法(Time.current也是调用now),使它们总是返回指定时间,然后执行block,最后恢复now、today方法。

此外还有travel可传时间段,指定“XX天前”,“XX小时后”。

如果block中要依赖两次调用now来计算执行时间,是做不到的,因为“总是返回指定时间”。

# activesupport-4.1.4/lib/active_support/testing/time_helpers.rb
module ActiveSupport
  module Testing
    class SimpleStubs
      Stub = Struct.new(:object, :method_name, :original_method)

      def initialize
        @stubs = {}
      end

      def stub_object(object, method_name, return_value)
        key = [object.object_id, method_name]

        if stub = @stubs[key]
          unstub_object(stub)
        end

        new_name = "__simple_stub__#{method_name}"

        @stubs[key] = Stub.new(object, method_name, new_name)

        object.singleton_class.send :alias_method, new_name, method_name
        object.define_singleton_method(method_name) { return_value }
      end

      def unstub_all!
        @stubs.each_value do |stub|
          unstub_object(stub)
        end
        @stubs = {}
      end

      private

        def unstub_object(stub)
          singleton_class = stub.object.singleton_class
          singleton_class.send :undef_method, stub.method_name
          singleton_class.send :alias_method, stub.method_name, stub.original_method
          singleton_class.send :undef_method, stub.original_method
        end
    end

    module TimeHelpers
      def travel(duration, &block)
        travel_to Time.now + duration, &block
      end

      def travel_to(date_or_time, &block)
        if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
          now = date_or_time.midnight.to_time
        else
          now = date_or_time.to_time
        end

        simple_stubs.stub_object(Time, :now, now)
        simple_stubs.stub_object(Date, :today, now.to_date)

        if block_given?
          begin
            block.call
          ensure
            travel_back
          end
        end
      end

      def travel_back
        simple_stubs.unstub_all!
      end

      private

        def simple_stubs
          @simple_stubs ||= SimpleStubs.new
        end
    end
  end
end