A PHP library for mocking date and time in tests

Related tags

Testing clock-mock
Overview

ClockMock

Slope s.r.l.

Latest Stable Version Total Downloads License

ClockMock provides a way for mocking the current timestamp used by PHP for \DateTime(Immutable) objects and date/time related functions. It requires the uopz extension (version >= 6.1.1).

This library is meant for development and testing only. It does not aim to propose a clock service to be used in production code, as we believe that you shouldn't need to do that when your only purpose is to mock the current time in testing code.

Why we built it

  • We were looking for a way to mock the native php date and time functions and classes without having to change our production code for it, and without having ot use any 3rd party library for handling dates/clocks.
  • For this purpose, we were previously using the php-timecop extension. The problem is that said extension never implemented support for PHP 7.4 onward. That extension currently does not even build for PHP 8.0.

Installation

You can install the library using Composer. Run the following command to install the latest version from Packagist:

composer require --dev slope-it/clock-mock

Note that, as this is not a tool intended for production, it should be required only for development (--dev flag).

Mocked functions/methods

  • date()
  • date_create()
  • date_create_immutable()
  • getdate()
  • gmdate()
  • idate()
  • localtime()
  • microtime()
  • strtotime()
  • time()
  • DateTime::__construct
  • DateTimeImmutable::__construct

Functions/methods with missing mocks (HELP NEEDED!)

  • date_create_from_format()
  • date_create_immutable_from_format()
  • gettimeofday()
  • gmmktime()
  • gmstrftime()
  • mktime()
  • strftime()
  • unixtojd()
  • DateTime::createFromFormat
  • DateTimeImmutable::createFromFormat
  • $_SERVER['REQUEST_TIME']

Usage

1. Stateful API

You can call ClockMock::freeze with a \DateTime or \DateTimeImmutable. Any code executed after it will use that specific date and time as the current timestamp. Call ClockMock::reset when done to restore real, current time.

Example:

<?php

use PHPUnit\Framework\TestCase;
use SlopeIt\ClockMock\ClockMock;

class MyTestCase extends TestCase
{
    public function test_something_using_stateful_mocking_api()
    {
        ClockMock::freeze(new \DateTime('1986-06-05'));
        
        // Code executed in here, until ::reset is called, will use the above date and time as "current"
        $nowYmd = date('Y-m-d');
        
        ClockMock::reset();
        
        $this->assertEquals('1986-06-05', $nowYmd);
    }
}

2. Stateless API

The library also provides a closure-based API that will execute the provided code at a specific point in time. This API does not need manually freezing or re-setting time, so it can be less error prone in some circumstances.

Example:

<?php

use PHPUnit\Framework\TestCase;
use SlopeIt\ClockMock\ClockMock;

class MyTestCase extends TestCase
{
    public function test_something_using_stateless_mocking_api()
    {
        $nowYmd = ClockMock::executeAtFrozenDateTime(new \DateTime('1986-06-05'), function () {
            // Code executed in here will use the above date and time as "current"
            return date('Y-m-d');
        });
        
        $this->assertEquals('1986-06-05', $nowYmd);
    }
}

How to contribute

  • Did you find and fix any bugs in the existing code?
  • Do you want to contribute a new feature, or a missing mock?
  • Do you think documentation can be improved?

Under any of these circumstances, please fork this repo and create a pull request. We are more than happy to accept contributions!

Credits

  • php-timecop, as ClockMock was inspired by it.
  • ext-uopz, as ClockMock is just a very thin layer on top of the amazing uopz extension, which provides a very convenient way to mock any function or method, including the ones of the php stdlib, at runtime.

Maintainer

@andreasprega

Comments
  • "DateTimeMock::__construct()" side-effect using "idate()" when setting microseconds...

    Hi,

    when using a non GMT DateTimezone and setting microseconds via

    $this->setTime(
                    idate('H', $this->getTimestamp()),
                    idate('i', $this->getTimestamp()),
                    idate('s', $this->getTimestamp()),
                    (int) ClockMock::getFrozenDateTime()->format('u')
                );
    

    the hour/minute is wrongly modified as idate() works on unix GMT-based timestamp. Example:

    Let's assume 02.04.2022 18:00:00 as Berlin time.

    $dt = (new DateTime('now', new DateTimeZone('Europe/Berlin')))
    

    DateTime now contains:

    date: 2022-04-02 18:00:00.0 Europe/Berlin (+02:00)

    Now using

    $dt->setTime(
                    idate('H', $dt->getTimestamp()),
                    idate('i', $dt->getTimestamp()),
                    idate('s', $dt->getTimestamp())
    );
    

    $dt contains:

    date: 2022-04-02 16:00:00.0 Europe/Berlin (+02:00).

    This means the time is now set 2 hours back, but still the Europe/Berlin timezone.

    Solution in DateTimeMock:

    Use

    $this->setTime(
                      (int) $this->format('H'),
                      (int) $this->format('i'),
                      (int) $this->format('s'),
                      (int) ClockMock::getFrozenDateTime()->format('u')
    );
    

    as this does't modify hour/minute/second when setting microseconds...

    -mimo

    opened by lfmimo 6
  • Handle 'U' format in createFromFormat.

    Handle 'U' format in createFromFormat.

    Symfony 4.4 uses this strange construction to get the current time:

    DateTime::createFromFormat('U', time())
    

    In normal PHP that works fine, but the mocked version was throwing

    TypeError: DateTime::createFromFormat() expects parameter 2 to be string, int given
    

    This explicitly stringifies the $datetime parameter to match the behavior of the vanilla implementation. It also simplifies the workaround for createFromFormat returning the current time when the format doesn't contain any time parts. The prior overcomplicated version didn't work with the relative date structure returned for the 'U' format.

    opened by Elemecca 3
  • DateTime immutable doesn't use formatted timezone

    DateTime immutable doesn't use formatted timezone

    DateTimeImmutableMock is created by formatting a new DateTimeMock, and thus avoiding code duplication.

    However, sometimes the timezone can be passed in the date format (for example: "2022-03-15 14:06:30+02:00").

    The previous format used to create the immutable date from the mutable one (which was "Y-m-d H:i:s.u") doesn't include the date timezone. Because of this, the timezone of the immutable date is lost when it is passed in a formatted date.

    opened by syffer 3
  • fix: add support for create from epoch

    fix: add support for create from epoch

    Fixes https://github.com/slope-it/clock-mock/issues/17

    Its pretty common with Symfonys time mocking to do \DateTime::createFromFormat('U', strval(time())); re: strict types Ive added the types otherwise you just get a type error from the int on time() also downstream from dateparse.

    Screenshot 2022-06-08 at 17 00 35
    opened by gsdevme 2
  • Problems with specifying datetime and timezone

    Problems with specifying datetime and timezone

    Hello, I have a problem that when I create an instance with $datetime and $timezone, it returns different results from Datetime.

    Specifically, the case is as follows.

    $expectedDatetimeFormat = '1986-06-05 12:13:14';
    $timezone = new \DateTimeZone('Asia/Tokyo'); // +09:00
    
    // not mocked Datetime
    // This test succeed
    $asiaTokyoTimezone = new \DateTime('1986-06-05 12:13:14', $timezone);
    $this->assertSame($expectedDatetimeFormat, $asiaTokyoTimezone->format('Y-m-d H:i:s'));
    
    // mocked Datetime
    // This test fails
    ClockMock::freeze(new \DateTime('2022-11-21 00:00:00'));
    $asiaTokyoTimezone = new \DateTime('1986-06-05 12:13:14', $timezone);
    $this->assertSame($expectedDatetimeFormat, $asiaTokyoTimezone->format('Y-m-d H:i:s')); // actual: 1986-06-05 21:13:14
    

    Solution: Do try to set the $datetime with timezone when __construct. In src/DateTimeMock.php:

    // ..
    if ($datetime !== 'now' && $timezone !== null) {
        $datetime = $datetime . ' ' . $timezone->getName();
    }
    parent::__construct($datetime, $timezone);
    // ...
    
    opened by hum2 1
  • PHP Engine requirement

    PHP Engine requirement

    Why PHP 7.1 is declared as not supported ? I see that uopz supports PHP 7.1+ , and nothing in the source code let me think that it's only compatible with PHP 7.4. Is there any particular reason ?

    opened by JesusTheHun 1
  • `mock_date_create_from_format` throws `TypeError`

    `mock_date_create_from_format` throws `TypeError`

    The following sequence of calls causes TypeError: DateTime::setTime() expects parameter 4 to be int, float given in clock-mock/src/ClockMock.php:177:

    $now = new DateTime("2021-10-13 12:44:52.8549178");
    ClockMock::freeze($now);
    
    // next line throws
    $datetime = DateTime::createFromFormat("Y-m-d H:i:s", "2021-10-13 12:44:52");
    

    The reason is because date_parse returns this:

    ^ array:12 [
      "year" => 2021
      "month" => 10
      "day" => 13
      "hour" => 12
      "minute" => 44
      "second" => 52
      "fraction" => 0.0 <--- HERE
      "warning_count" => 0
      "warnings" => []
      "error_count" => 0
      "errors" => []
      "is_localtime" => false
    ]
    
    opened by BladeMF 1
  • `DateTime::createFromFormat` with `now` throws an error: `Call to a member function setTime() on bool`

    `DateTime::createFromFormat` with `now` throws an error: `Call to a member function setTime() on bool`

    Hello,

    I'm having an issue when I want to instantiate a DateTime using the createFromFormat method if I specify the format as now and the datetime as a date with no time (ex: 2022-08-04).

    Example:

    ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14'));
    $dateTime = \DateTime::createFromFormat('\n\o\w', '2022-01-01');
    

    Output:

    Error: Call to a member function setTime() on bool
    

    Expected: The createFromFormat method must return false if the date doesn't match the format.

    Solution: Do not try to set the time when \DateTime::createFromFormat returns false. In src/ClockMock.php: Replace

    private static function mock_date_create_from_format(): callable
    {
    // ...
    if ($parsedDate['hour'] === false) {
    // ..
    }
    

    With:

    private static function mock_date_create_from_format(): callable
    {
    // ...
    if ($parsedDate['hour'] === false && $dateTimeObject !== false) {
    // ...
    }
    

    ~ faille76

    opened by faille76 0
  • `ClockMock::reset()` does not reset createFromFormat

    `ClockMock::reset()` does not reset createFromFormat

    Hi,

    Using the ClockMock::freeze influences my other test cases with PHPUnit, even doing a ClockMock::reset.

    The specific case is when creating a DateTime with the createFromFormat method. But also via DateTimeImmutable, and the other php functions date_create_from_format, date_create_immutable_from_format.

    Example:

    ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14'));
    ClockMock::reset();
    
    $dateTime = \DateTime::createFromFormat('\n\o\w', 'now');
    

    Output:

    Error: Call to a member function format() on null
    

    Solution: Reset the functions and methods with uopz_unset_return and uopz_unset_return when calling ClockMock::reset()

    In src/ClockMock.php: Add the following code in the reset method:

    public static function reset(): void
    {
          // ...
          uopz_unset_return('date_create_from_format');
          uopz_unset_return('date_create_immutable_from_format');
          uopz_unset_return(\DateTime::class, 'createFromFormat');
          uopz_unset_return(\DateTimeImmutable::class, 'createFromFormat');
          // ...
    }
    

    Test case:

    public function test_date_create_from_format_freeze_reset_must_not_use_mock()
    {
        ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14'));
        ClockMock::reset();
    
        $dateTime = \DateTime::createFromFormat('\n\o\w', 'now');
    
        $this->assertInstanceOf(\DateTime::class, $dateTime);
    }
    

    ~faille76

    opened by faille76 0
  • Move to php-8.1 compatibility

    Move to php-8.1 compatibility

    This likely need to be tagged with 0.4.x as removing deprecated functions shouldn't affect former php versions.

    Not sure if there's a need to implement compatible mocks for these

    opened by ppp0 0
Releases(0.3.4)
  • 0.3.4(Nov 22, 2022)

    What's Changed

    • Issue #26: fix mocked \DateTime when constructed with absolute string and a specified timezone by @asprega in https://github.com/slope-it/clock-mock/pull/28

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.3.3...0.3.4

    Source code(tar.gz)
    Source code(zip)
  • 0.3.3(Oct 14, 2022)

    What's Changed

    • Fix microtime(false) return value format by @ximarx in https://github.com/slope-it/clock-mock/pull/25

    New Contributors

    • @ximarx made their first contribution in https://github.com/slope-it/clock-mock/pull/25

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.3.2...0.3.3

    Source code(tar.gz)
    Source code(zip)
  • 0.3.2(Aug 8, 2022)

    What's Changed

    • Reset methods createFromFormat after ClockMock::reset() by @faille76 in https://github.com/slope-it/clock-mock/pull/21
    • Fix createFromFormat with now format by @faille76 in https://github.com/slope-it/clock-mock/pull/23

    New Contributors

    • @faille76 made their first contribution in https://github.com/slope-it/clock-mock/pull/21

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.3.1...0.3.2

    Source code(tar.gz)
    Source code(zip)
  • 0.3.1(Jun 20, 2022)

    What's Changed

    • Handle 'U' format in createFromFormat. by @Elemecca in https://github.com/slope-it/clock-mock/pull/19

    New Contributors

    • @Elemecca made their first contribution in https://github.com/slope-it/clock-mock/pull/19

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.3.0...0.3.1

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(May 29, 2022)

    What's Changed

    • Add mock for strftime and gmstrftime by @bailey-spencer in https://github.com/slope-it/clock-mock/pull/12
    • Add mock for gettimeofday by @bailey-spencer in https://github.com/slope-it/clock-mock/pull/9
    • Add mock for unixtojd by @bailey-spencer in https://github.com/slope-it/clock-mock/pull/11
    • Add mock for mktime and gmmktime by @bailey-spencer in https://github.com/slope-it/clock-mock/pull/10
    • Also update $_SERVER[REQUEST_TIME(_FLOAT)] when freezing. by @asprega in https://github.com/slope-it/clock-mock/pull/15
    • Add mutable and immutable ::createFromFormat + functional aliases by @asprega in https://github.com/slope-it/clock-mock/pull/16

    New Contributors

    • @bailey-spencer made their first contribution in https://github.com/slope-it/clock-mock/pull/12

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.2.2...0.3.0

    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Apr 4, 2022)

    What's Changed

    • Fix to wrong time when a DateTime with a specific timezone is created while ClockMock is active by @lfmimo in https://github.com/slope-it/clock-mock/pull/8

    New Contributors

    • @lfmimo made their first contribution in https://github.com/slope-it/clock-mock/pull/8

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.2.1...0.2.2

    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Mar 25, 2022)

    What's Changed

    • Add gmdate mock and refactor mocks with their own private methods by @asprega in https://github.com/slope-it/clock-mock/pull/3
    • DateTime immutable doesn't use formatted timezone by @syffer in https://github.com/slope-it/clock-mock/pull/4

    New Contributors

    • @syffer made their first contribution in https://github.com/slope-it/clock-mock/pull/4

    Full Changelog: https://github.com/slope-it/clock-mock/compare/0.2.0...0.2.1

    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(May 24, 2021)

Owner
Slope
Slope engineering team
Slope
To run time/IO related unit tests (e.g., sleep function calls, database queries, API calls, etc) faster using Swoole.

To run time/IO related unit tests (e.g., sleep function calls, database queries, API calls, etc) faster using Swoole.

Demin Yin 11 Sep 9, 2022
The most powerful and flexible mocking framework for PHPUnit / Codeception.

AspectMock AspectMock is not an ordinary PHP mocking framework. With the power of Aspect Oriented programming and the awesome Go-AOP library, AspectMo

Codeception Testing Framework 777 Dec 12, 2022
PHP Mocking Framework

Phake Phake is a framework for PHP that aims to provide mock objects, test doubles and method stubs. Phake was inspired by a lack of flexibility and e

Phake 469 Dec 2, 2022
Highly opinionated mocking framework for PHP 5.3+

Prophecy Prophecy is a highly opinionated yet very powerful and flexible PHP object mocking framework. Though initially it was created to fulfil phpsp

PHPSpec Framework 8.5k Jan 3, 2023
PHP Mocking Framework

Phake Phake is a framework for PHP that aims to provide mock objects, test doubles and method stubs. Phake was inspired by a lack of flexibility and e

Phake 469 Dec 2, 2022
Add mocking capabilities to Pest or PHPUnit

This repository contains the Pest Plugin Mock. The Mocking API can be used in regular PHPUnit projects. For that, you just have to run the following c

PEST 16 Dec 3, 2022
Some shorthand functions for skipping and focusing tests.

Pest Plugin: Shorthands This repository contains the Pest Plugin Shorthands. If you want to start testing your application with Pest, visit the main P

Thomas Le Duc 10 Jun 24, 2022
Mock HTTP requests on the server side in your PHP unit tests

HTTP Mock for PHP Mock HTTP requests on the server side in your PHP unit tests. HTTP Mock for PHP mocks the server side of an HTTP request to allow in

InterNations GmbH 386 Dec 27, 2022
Extension to use built-in PHP server on Behat tests

Extension to use built-in PHP server on Behat tests Instalation composer require libresign/behat-builtin-extension Configuration Add the extension to

LibreSign 2 Feb 21, 2022
Enforce consistent styling for your Pest PHP tests

A set of PHP CS rules for formatting Pest PHP tests.

Worksome 2 Mar 15, 2022
PHP Test Generator - A CLI tool which generates unit tests

This project make usages of PHPStan and PHPParser to generate test cases for a given PHP File.

Alexander Schranz 7 Dec 3, 2022
vfsStream is a stream wrapper for a virtual file system that may be helpful in unit tests to mock the real file system. It can be used with any unit test framework, like PHPUnit or SimpleTest.

vfsStream vfsStream is a stream wrapper for a virtual file system that may be helpful in unit tests to mock the real file system. It can be used with

null 1.4k Dec 23, 2022
Magic Test allows you to write browser tests by simply clicking around on the application being tested, all without the slowness of constantly restarting the testing environment.

Magic Test for Laravel Magic Test allows you to write browser tests by simply clicking around on the application being tested, all without the slownes

null 400 Jan 5, 2023
Wraps your Pest suite in a Laravel application instance, allowing global use of the framework in tests.

Pest Larastrap Plugin This is currently a highly experimental project and is subject to large pre-release changes. Pest PHP is an awesome PHP testing

Luke Downing 3 Jan 6, 2022
TestDummy makes the process of preparing factories (dummy data) for your integration tests as easy as possible

TestDummy TestDummy makes the process of preparing factories (dummy data) for your integration tests as easy as possible. As easy as... Build a Post m

Laracasts 461 Sep 28, 2022
A tool to run migrations prior to running tests

cakephp-test-migrator A tool to run migrations prior to running tests The Migrator For CakePHP 3.x composer require --dev vierge-noire/cakephp-test-mi

Vierge Noire 11 Apr 29, 2022
This plugin adds basic HTTP requests functionality to Pest tests, using minicli/curly

Curly Pest Plugin This plugin adds basic HTTP requests functionality to Pest tests, using minicli/curly. Installation composer require minicli/pest-pl

minicli 16 Mar 24, 2022
Example repo for writing tests in Drupal (using DDEV)

Drupal Test Writing This is a test D9 site which can be used for practicing test writing and running.

David Stinemetze 13 Nov 14, 2022
Report high memory usage PHPUnit tests: Managed by opg-org-infra & Terraform

phpunit-memory-usage Report high memory usage PHPUnit tests: Managed by opg-org-infra & Terraform Configuration Add into the phpunit.xml extensions se

Ministry of Justice 2 Aug 4, 2022