我希望能够在DateTime
PHPUnit 或 Behat 测试期间为每个实例化实例设置时间。
我正在测试与时间相关的业务逻辑。例如,类中的方法只返回过去或未来的事件。
如果可能的话,我不想做的事情:
编写一个包装器
DateTime
并使用它而不是DateTime
贯穿我的代码。这将涉及对我当前的代码库进行一些重写。每次运行测试/套件时动态生成数据集。
所以问题是:是否可以覆盖DateTime
s 行为以始终在请求时提供特定时间?
您应该存根DateTime
测试中需要的方法以返回预期值。
$stub = $this->getMock('DateTime');
$stub->expects($this->any())
->method('theMethodYouNeedToReturnACertainValue')
->will($this->returnValue('your certain value'));
见https://phpunit.de/manual/current/en/test-doubles.html
如果您无法存根方法,因为它们被硬编码到您的代码中,请查看
它解释了如何在调用时调用回调new
。然后,您可以将 DateTime 类替换为具有固定时间的自定义 DateTime 类。另一种选择是使用http://antecedent.github.io/patchwork
您还可以使用时间旅行者库,它使用 aop php pecl 扩展来带来类似于 ruby monkey 修补的东西https://github.com/rezzza/TimeTraveler
还有这个 php 扩展,灵感来自 ruby timecop 一: https ://github.com/hnw/php-timecop
除了@Gordon 已经指出的内容之外,还有一种相当老套的方法来测试依赖于当前时间的代码:
我只模拟了一个受保护的方法,它可以让你获得“全局”值,你可以解决需要自己创建一个类的问题,你可以要求诸如当前时间之类的东西(这会更干净,但在 php 中它是有争议的/人们不想这样做是可以理解的)。
看起来像这样:
class Calendar {
public function getCurrentTimeAsISO() {
return $this->currentTime()->format('Y-m-d H:i:s');
}
protected function currentTime() {
return new DateTime();
}
}
class CalendarTest extends PHPUnit_Framework_TestCase {
public function testCurrentDate() {
$cal = $this->getMockBuilder('Calendar')
->setMethods(array('currentTime'))
->getMock();
$cal->expects($this->once())
->method('currentTime')
->will($this->returnValue(
new DateTime('2011-01-01 12:00:00')
)
);
$this->assertSame(
'2011-01-01 12:00:00',
$cal->getCurrentTimeAsISO()
);
}
}
您可以更改实现以DateTime()
显式实例化time()
:
new \DateTime("@".time());
这不会改变您班级的行为。但是现在您可以通过提供命名空间函数来模拟:time()
namespace foo;
function time() {
return 123;
}
你也可以使用我的包php-mock/php-mock-phpunit这样做:
namespace foo;
use phpmock\phpunit\PHPMock;
class DateTimeTest extends \PHPUnit_Framework_TestCase {
use PHPMock;
public function testDateTime() {
$time = $this->getFunctionMock(__NAMESPACE__, "time");
$time->expects($this->once())->willReturn(123);
$dateTime = new \DateTime("@".time());
$this->assertEquals(123, $dateTime->getTimestamp());
}
}
当我使用Symfony 的 WebTestCase使用 PHPUnit 测试包执行功能测试时,模拟 DateTime 类的所有用法很快变得不切实际。
我想测试应用程序随着时间的推移处理请求,例如测试 cookie 或缓存过期等。
我发现这样做的最好方法是实现我自己的扩展默认类的 DateTime 类,并提供一些静态方法以允许将默认时间偏差添加/减去从该点开始创建的所有 DateTime 对象.
这是一个非常容易实现的功能,并且不需要安装自定义库。
注意事项:此方法的唯一缺点是 Symfony 框架(或您正在使用的任何框架)不会使用您的库,因此它预期自己处理的任何行为,例如内部缓存/cookie 过期,都不会受这些变化的影响。
namespace My\AppBundle\Util;
/**
* Class DateTime
*
* Allows unit-testing of DateTime dependent functions
*/
class DateTime extends \DateTime
{
/** @var \DateInterval|null */
private static $defaultTimeOffset;
public function __construct($time = 'now', \DateTimeZone $timezone = null)
{
parent::__construct($time, $timezone);
if (self::$defaultTimeOffset && $this->isRelativeTime($time)) {
$this->modify(self::$defaultTimeOffset);
}
}
/**
* Determines whether to apply the default time offset
*
* @param string $time
* @return bool
*/
public function isRelativeTime($time)
{
if($time === 'now') {
//important, otherwise we get infinite recursion
return true;
}
$base = new \DateTime('2000-01-01T01:01:01+00:00');
$base->modify($time);
$test = new \DateTime('2001-01-01T01:01:01+00:00');
$test->modify($time);
return ($base->format('c') !== $test->format('c'));
}
/**
* Apply a time modification to all future calls to create a DateTime instance relative to the current time
* This method does not have any effect on existing DateTime objects already created.
*
* @param string $modify
*/
public static function setDefaultTimeOffset($modify)
{
self::$defaultTimeOffset = $modify ?: null;
}
/**
* @return int the unix timestamp, number of seconds since the Epoch (Jan 1st 1970, 00:00:00)
*/
public static function getUnixTime()
{
return (int)(new self)->format('U');
}
}
使用它很简单:
public class myTestClass() {
public function testMockingDateTimeObject()
{
echo "fixed: ". (new DateTime('18th June 2016'))->format('c') . "\n";
echo "before: ". (new DateTime('tomorrow'))->format('c') . "\n";
echo "before: ". (new DateTime())->format('c') . "\n";
DateTime::setDefaultTimeOffset('+25 hours');
echo "fixed: ". (new DateTime('18th June 2016'))->format('c') . "\n";
echo "after: ". (new DateTime('tomorrow'))->format('c') . "\n";
echo "after: ". (new DateTime())->format('c') . "\n";
// fixed: 2016-06-18T00:00:00+00:00 <-- stayed same
// before: 2016-09-20T00:00:00+00:00
// before: 2016-09-19T11:59:17+00:00
// fixed: 2016-06-18T00:00:00+00:00 <-- stayed same
// after: 2016-09-21T01:00:00+00:00 <-- added 25 hours
// after: 2016-09-20T12:59:17+00:00 <-- added 25 hours
}
}