Mocking The Time, используемое всеми экземплярами DateTime для целей тестирования.
Я хотел бы указать время для каждого экземпляра DateTime, созданного на время PHPUnit или Behat Test.
Я тестирую бизнес-логику, относящуюся ко времени. Например, метод в классе возвращает события только в прошлом или в будущем.
Вещь, которую я не хочу делать, если это возможно:
1) Напишите обертку вокруг DateTime и используйте это вместо DateTime на протяжении всего моего кода. Это потребует повторной записи моей текущей базы кода.
2) Динамически генерировать набор данных каждый раз, когда выполняется тест/пакет.
Итак, вопрос: возможно ли переопределить поведение DateTimes, чтобы всегда задавать определенное время по запросу?
Ответы
Ответ 1
Вы должны заглушить методы DateTime, которые вам нужны в тестах, чтобы вернуть ожидаемые значения.
$stub = $this->getMock('DateTime');
$stub->expects($this->any())
->method('theMethodYouNeedToReturnACertainValue')
->will($this->returnValue('your certain value'));
См. http://www.phpunit.de/manual/3.6/en/test-doubles.html
Если вы не можете заглушить методы, потому что они жестко закодированы в ваш код, посмотрите
который объясняет, как вызывать обратный вызов всякий раз, когда вызывается new
. Затем вы можете заменить класс DateTime на пользовательский класс DateTime с фиксированным временем. Другим вариантом было бы использовать http://antecedent.github.io/patchwork
Ответ 2
Вы также можете использовать библиотеку времени путешественника, которая использует расширение aop php pecl, чтобы принести вещи, похожие на рубиновое обезглавливание обезьян https://github.com/rezzza/TimeTraveler
Там также это расширение php, вдохновленное рубином timecop one:
https://github.com/hnw/php-timecop
Ответ 3
Добавляя к тому, что уже сказал @Gordon, есть один, довольно хакерский способ проверки кода, который зависит от текущего времени:
Мое издевательствование только одного защищенного метода, который дает вам "глобальное" значение, которое вы можете обойти проблемы, связанные с необходимостью создания класса самостоятельно, чтобы вы могли запрашивать такие вещи, как текущее время (которое было бы чище, но в php it является спорным/понятным, что люди не хотят этого делать).
Это будет выглядеть примерно так:
<?php
class Calendar {
public function getCurrentTimeAsISO() {
return $this->currentTime()->format('Y-m-d H:i:s');
}
protected function currentTime() {
return new DateTime();
}
}
?>
<?php
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()
);
}
}
Ответ 4
Вы можете изменить свою реализацию, чтобы явно создать DateTime()
с помощью time()
:
new \DateTime("@".time());
Это не изменяет поведение вашего класса. Но теперь вы можете mock 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());
}
}
Ответ 5
Поскольку я использую Symfony WebTestCase для выполнения функционального тестирования с использованием пакета тестирования PHPUnit, быстро стало непрактично издеваться над всеми обычаями DateTime класс.
Я хотел протестировать приложение, поскольку он обрабатывает запросы с течением времени, такие как тестирование cookie или истечения срока действия кэша и т.д.
Лучший способ, который я нашел для этого, - реализовать собственный класс DateTime, который расширяет класс по умолчанию, и предоставляет некоторые статические методы, позволяющие добавлять/вычитать по умолчанию временные перекосы ко всем объектам DateTime, созданным из этот пункт далее.
Это очень простая функция для реализации и не требует установки пользовательских библиотек.
caveat emptor: Единственный недостаток этого метода заключается в том, что инфраструктура 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
}
}