How can I test Perl applications using the system time?

I have a web application where I want to run some system tests and for that I will need to move the system time. The app uses DateTime all over.

Does anyone have any recommendations on how to change the time that DateTime-> now reports? The only thing that comes to mind is subclassing DateTime and messing around with all the lines of use, but that seems pretty invasive.

Note on answers:

All three will work fine, but Hook :: LexWrap is one - the one I chose because (a) I want to move the clock, not move it slightly (which is more the purpose of what Time :: Layout and friends do); (b) I use DateTime consistently and I am glad that errors appear if I did not accidentally use it; and (c) The :: LexWrap hook is simply more nifty than the symbol table hack for anything that does the same thing. (Also, it turns out to be dependent on some module that I already installed, so I didn't even have to use CPAN ...)

+2


source to share


4 answers


You can use Hook :: LexWrap to intercept the now () method.



use Hook::LexWrap;

use DateTime;

# Use real now
test();

{
    my $wrapper = wrap 'DateTime::now',
        post => sub {
            $_[-1] = DateTime->from_epoch( epoch => 0 );
        };

    # Use fake now
    test();

}

# use real now again
test();

sub test {
    my $now = DateTime->now;

    print "The time is $now\n";
}

      

+1


source


Instead of taking a high-level approach and wrapping specifically DateTime

, you might want to look into the Test :: MockTime and Time :: Mock modules , which override low-level functions, which DateTime

, etc. use, and (with some luck) will do the right thing on any time sensitive code. This seems like a more reliable way of testing to me.



+11


source


I think Hook :: LexWrap is too strong for this situation. It's easier to just override such a simple function.

use DateTime;

my $offset;

BEGIN {
  $offset = 24 * 60 * 60; # Pretend it tomorrow

  no warnings 'redefine';

  sub DateTime::now
  {
    shift->from_epoch( epoch => ($offset + scalar time), @_ )
  }
} # end BEGIN

      

You can replace my $offset

with our $offset

if you need to access $offset

from outside the file that contains this code.

You can tweak $offset

at any time if you want to change the idea of โ€‹โ€‹the DateTime of the current time during the run.

The calculation $offset

is likely to be more complex than shown above. For example, to set the "current time" to an absolute time:

my $want = DateTime->new(
   year   => 2009,
   month  => 9,
   day    => 14,
   hour   => 12,
   minute => 0,
   second => 0,
   time_zone => 'America/Chicago',
);

my $current = DateTime->from_epoch(epoch => scalar time);

$offset = $want->subtract_datetime_absolute($current)->in_units('seconds');

      

But you probably want to calculate a fixed number of seconds to add to the current time so that the time afterwards will speed up. The problem with using add( days => 1 );

in an overridden method now

is that things like DST changes will cause the time to jump at the wrong pseudo-time.

+4


source


When developing a new class that can be checked, ideally, the ideal solution is to be able to introduce new date objects.

However, for existing code using DateTime->now

and DateTime->today

possibly a suitable solution, see below. I am including it here as a way to do this without introducing Hook :: LexWrap as a dependency and without affecting the behavior globally.

{
    no strict 'refs';
    no warnings 'redefine';
    local *{'DateTime::today'} = sub {
        return DateTime->new(
            year  => 2012,
            month => 5,
            day   => 31
        );
    };
    say DateTime->today->ymd(); # 2012-05-31
};

say DateTime->today->ymd(); # today

      

+1


source







All Articles