Reputation: 238707
I am testing a method on my class that does some date checking. The problem is that the method depends on today's date (which changes every day), which makes this difficult to test. How can I mock today's date so that my tests will still pass tomorrow?
Upvotes: 14
Views: 8357
Reputation: 1441
If your interest is to not pass in the date to preserve the external interface, then a good way to do this is to use a "seam" to provide the date:
class MyClass {
public function toBeTested() {
$theDate = $this->getDate();
...
}
protected function getDate() {
return date();
}
}
In general use, this class just works normally. Then, in your unit testing, instead of testing MyClass, you extend MyClass with an inner class that overrides the getDate() function:
use PHPUnit\Framework\TestCase;
class MyTest extends TestCase {
static $testDate;
public function testToBeTested() {
//set the date to be used
MyTest::testDate = '1/2/2000';
$classUnderTest = new MyClassWithDate();
$this->assertEquals('expected', $classUnderTest->toBeTested());
}
}
//just pass back the expected date
class MyClassWithDate extends MyClass {
protected function getDate() {
return MyTest::testDate;
}
}
In this code, you test against your extension of the real class, but your extension overrides the seam function (getDate()), and returns back the date that you want to use for this particular test.
Again, sorry if there are some egregious syntax errors, this was written freehand.
Upvotes: 6
Reputation: 2124
I couldn't rename twice as David says, so I got it like:
function mockDate()
{
runkit_function_rename('date', 'test_date_override');
runkit_function_add('date','$format=NULL,$timestamp=NULL,$locale=NULL', 'return DATEMOCK;');
}
function unmockDate()
{
runkit_function_remove('date');
runkit_function_rename('test_date_override', 'date');
}
Upvotes: 0
Reputation: 24159
I know you don't want to pass it in as an argument. But maybe you can rethink this ...
When being passed as a parameter from the outside, the date is not an insignificant technical detail, but a significant functional rule. Don't you need any of the following?
Upvotes: 3
Reputation: 36532
While Jon's answer is the "right way," another option is to use the runkit extension to temporarily replace the date()
and/or time()
functions with ones that return a fixed value for the test.
runkit.internal_override
in php.ini
so you can rename built-in functions.runkit_function_rename
.Here's some completely untested code to help with this:
function mock_function($original, $mock) {
runkit_function_rename($original, $original . '_original');
runkit_function_rename($mock, $original);
}
function unmock_function($original, $mock) {
runkit_function_rename($original, $mock);
runkit_function_rename($original . '_original', $original);
}
You should use these from within the setUp()
and tearDown()
methods to make sure you don't interfere with other tests that follow.
Upvotes: 4
Reputation: 1500385
I know nothing about PHP, but in both Java and C# I would pass in a "clock" of some description - not today's date itself, but an object which you can ask for the current date/time. Then in unit tests you can pass in an object which can give any date you want - including one that's hard-coded into the tests.
Does that work in PHP too?
Upvotes: 9