22

How can I change the current time (i.e. the output of time()) in PHP for unit-testing date-manipulation-class?

cezar
  • 11,616
  • 6
  • 48
  • 84
happytoad
  • 297
  • 3
  • 10
  • http://stackoverflow.com/questions/530649/is-it-possible-to-replace-monkeypatch-php-functions – Rob Olmos Nov 19 '10 at 02:15
  • Can you show some code? In my opinion, if you need to override `time()` for unit testing, there *might be* something wrong with either the test or the code. – netcoder Nov 19 '10 at 02:23
  • 1
    the class manipulate the current date. i just want to check that all methods words correctly in all dates. – happytoad Nov 19 '10 at 02:28
  • Why don't you just manipulate any date passed as a parameter? You'll get better coverage that way. – Erwin Alva Nov 19 '10 at 02:51
  • if it is still relevant to you, you can use https://github.com/rezzza/TimeTraveler this library can override system time to desired value. – Manish May 04 '16 at 09:45
  • 1
    `Carbon::setTestNow();` looks super helpful: http://carbon.nesbot.com/docs/#api-testing – Ryan Jul 31 '17 at 13:50

4 Answers4

6

I recently came up with a solution that is great if you are using PHP 5.3 namespaces. You can implement a new time() function inside your current namespace and create a shared resource where you set the return value in your tests. Then any unqualified call to time() will use your new function.

For further reading I described it in detail in my blog

Fabian Schmengler
  • 24,155
  • 9
  • 79
  • 111
  • 1
    I recently implemented the library [php-mock](https://github.com/malkusch/php-mock) which uses that language feature for mocking non deterministic PHP functions like `time()`. – Markus Malkusch Nov 26 '14 at 21:27
3

You might consider an interface-based inheritance model:

interface iClock
{
    public function getTime();

}

class SystemClock implements iClock
{
    public function getTime()
    {
        return time();
    }
}

class FakeClock implements iClock
{
    private $time;

    function __construct($time)
    {
        $this->time = $time;
    }

    public function getTime()
    {
        return $this->time;
    }

    // you could have more functions to advance the time, etc.
}

Your own classes that need to work with the current time would take a constructor parameter of the iClock interface type.

class YourOwnClass
{
    private $clock;

    function __construct(iClock $clock)
    {
        $this->clock = $clock;
    }

    function someFunction()
    {
        $now = $this->clock->getTime();

        // whatever
        return $now;
    }
}

Then you would simply inject the type of clock you wanted, depending on whether you were in a unit test or not:

$realWorldInstance = new YourOwnClass(new SystemClock());   
echo $realWorldInstance->someFunction();
$unitTestInstance = new YourOwnClass(new FakeClock(1234));
echo $unitTestInstance->someFunction();

BTW - I use this pattern often in .NET as well. It's also built into the IClock interface provided by Noda Time. I see no reason why this pattern wouldn't work in PHP. You might choose to return a DateTime instead of just the integer timestamp, but the above is the minimal needed to use the pattern.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
3

Sounds like you need to remove the dependency of time() from your date manipulation class

rojoca
  • 11,040
  • 4
  • 45
  • 46
  • so, i should change the logic of the class just for testing? – happytoad Nov 19 '10 at 02:32
  • 1
    @happytoad - no you just need to replace the call to `time()` in your date manipulation class to something you control. For example you could pass a time value to the function you are testing as a parameter, or, you could pass it to the constructor of your date class. – rojoca Nov 19 '10 at 02:32
  • 3
    so there isn't a way to change the time. it's weird that simple thing like changing the current time is not available for testing. bummer. thanks. – happytoad Nov 19 '10 at 02:37
3

My guess is you are struggling with your dependencies. It looks like you are using the time() function inside your class while with unit testing you want to make sure you control the output of the methods/classes that provide the time for you. I'd say create a Time class that provides you with the time and mock it into your unit tests.

ChrisR
  • 14,370
  • 16
  • 70
  • 107