Php 模拟所有DateTime实例用于测试的时间
我希望能够为PHPUnit或Behat测试期间实例化的Php 模拟所有DateTime实例用于测试的时间,php,testing,datetime,phpunit,behat,Php,Testing,Datetime,Phpunit,Behat,我希望能够为PHPUnit或Behat测试期间实例化的DateTime的每个实例设置时间 我正在测试与时间相关的业务逻辑。例如,类中的方法只返回过去或将来的事件 如果可能的话,我不想做的事情是: 围绕DateTime编写一个包装器,并在我的整个代码中使用它而不是DateTime。这将需要对我当前的代码库进行一点重写 每次运行测试/套件时动态生成数据集 因此,问题是:是否可以覆盖DateTimes行为,以便在请求时始终提供特定的时间 您应该存根测试中需要的DateTime方法以返回预期值 $stu
DateTime
的每个实例设置时间
我正在测试与时间相关的业务逻辑。例如,类中的方法只返回过去或将来的事件
如果可能的话,我不想做的事情是:
DateTime
编写一个包装器,并在我的整个代码中使用它而不是DateTime
。这将需要对我当前的代码库进行一点重写因此,问题是:是否可以覆盖
DateTime
s行为,以便在请求时始终提供特定的时间 您应该存根测试中需要的DateTime
方法以返回预期值
$stub = $this->getMock('DateTime');
$stub->expects($this->any())
->method('theMethodYouNeedToReturnACertainValue')
->will($this->returnValue('your certain value'));
看
如果由于方法硬编码到代码中而无法存根,请查看
- 塞巴斯蒂安·伯格曼
它解释了在调用
new
时如何调用回调。然后可以用具有固定时间的自定义DateTime类替换DateTime类。另一种选择是使用在@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()
);
}
}
您还可以使用time traveler lib,它使用aop php pecl扩展来实现类似于ruby monkey补丁的功能 还有一个php扩展,灵感来自ruby timecop one:
您可以使用
time()
显式地将实现更改为实例化DateTime()
:
这不会改变你们班的行为。但现在,您可以通过提供名称空间函数:
namespace foo;
function time() {
return 123;
}
您也可以使用我的软件包执行此操作:
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());
}
}
当我使用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
}
}
公共类myTestClass(){
公共函数testMockingDateTimeObject()
{
echo“fixed:”(新日期时间('2016年6月18日'))->格式('c')。“\n”;
echo“before:”((新日期时间('明天'))->格式('c')。“\n”;
echo“before:”((new DateTime())->格式('c')。“\n”;
DateTime::setDefaultTimeOffset(“+25小时”);
echo“fixed:”(新日期时间('2016年6月18日'))->格式('c')。“\n”;
echo“after:”((新日期时间('明天'))->格式('c')。“\n”;
echo“after:”(new DateTime())->格式('c')。“\n”;
//修正:2016-06-18T00:00:00+00:00谢谢Gordon-DateTime依赖项在我的大多数代码中都是硬编码的。我犯了一个错误,把它作为原语使用。所有其他依赖项都被注入,因此很容易模拟。我不想使用扩展来模拟,因为这会降低代码的可移植性。尽管这可能是唯一的选择!谢谢这是你的答案。很难通过这种方式检查日期比较。你还没有接受答案。你能澄清一下你在答案中寻找的是什么,以及为什么给出的答案不能让你满意。同样的问题,来自@shouze的答案的php timecop扩展非常有效。我想…但我没有特权我最后一次看到的时间旅行者坏了。时间警察的选择更好。
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
}
}