... not knowing if \'mock\' is the right word.
Anyway, I have an inherited code-base that I\'m trying to write some tests for that are time-based. Trying not to be <
Personally, I keep using time() in the tested functions/methods. In your test code, just make sure to not test for equality with time(), but simply for a time difference of less than 1 or 2 (depending on how much time the function takes to execute)
Disclaimer: I wrote this library.
You can mock time for test using Clock from ouzo-goodies.
In code use simply:
$time = Clock::now();
Then in tests:
Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);
Using [runkit][1] extension:
define('MOCK_DATE', '2014-01-08');
define('MOCK_TIME', '17:30:00');
define('MOCK_DATETIME', MOCK_DATE.' '.MOCK_TIME);
private function mockDate()
{
runkit_function_rename('date', 'date_real');
runkit_function_add('date','$format="Y-m-d H:i:s", $timestamp=NULL', '$ts = $timestamp ? $timestamp : strtotime(MOCK_DATETIME); return date_real($format, $ts);');
}
private function unmockDate()
{
runkit_function_remove('date');
runkit_function_rename('date_real', 'date');
}
You can even test the mock like this:
public function testMockDate()
{
$this->mockDate();
$this->assertEquals(MOCK_DATE, date('Y-m-d'));
$this->assertEquals(MOCK_TIME, date('H:i:s'));
$this->assertEquals(MOCK_DATETIME, date());
$this->unmockDate();
}
Carbon::setTestNow(Carbon $time = null)
makes any call to Carbon::now()
or new Carbon('now')
return the same time.
https://medium.com/@stefanledin/mock-date-and-time-with-carbon-8a9f72cb843d
Example:
public function testSomething()
{
$now = Carbon::now();
// Mock Carbon::now() / new Carbon('now') to always return the same time
Carbon::setTestNow($now);
// Do the time sensitive test:
$this->retroEncabulator('prefabulate')
->assertJsonFragment(['whenDidThisHappen' => $now->timestamp])
// Release the Carbon::now() mock
Carbon::setTestNow();
}
The $this->retroEncabulator()
function needs to use Carbon::now()
or new Carbon('now')
internally of course.
I recently came up with another 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
In most cases this will do. It has some advantages:
It's using phpunit, but you can addapt it to any other testing framework, you just need function that works like assertContains() from phpunit.
1) Add below function to your test class or bootstrap. Default tolerance for time is 2 secs. You can change it by passing 3rd argument to assertTimeEquals or modify function args.
private function assertTimeEquals($testedTime, $shouldBeTime, $timeTolerance = 2)
{
$toleranceRange = range($shouldBeTime, $shouldBeTime+$timeTolerance);
return $this->assertContains($testedTime, $toleranceRange);
}
2) Testing example:
public function testGetLastLogDateInSecondsAgo()
{
// given
$date = new DateTime();
$date->modify('-189 seconds');
// when
$this->setLastLogDate($date);
// then
$this->assertTimeEquals(189, $this->userData->getLastLogDateInSecondsAgo());
}
assertTimeEquals() will check if array of (189, 190, 191) contains 189.
This test should be passed for correct working function IF executing test function takes less then 2 seconds.
It's not perfect and super-accurate, but it's very simple and in many cases it's enough to test what you want to test.