2011-10-31 12 views
14

Me gustaría poder establecer el tiempo para cada instancia de DateTime instanciada durante la duración de una PHPUnit o Behat Test.Mocking The Time utilizado por todas las instancias de DateTime para fines de prueba.

Estoy probando la lógica de negocios relacionada con el tiempo. Por ejemplo, que un método en una clase solo devuelve eventos en el pasado o en el futuro.

cosa de que no quiero hacer, si es posible:

1) una envoltura alrededor de DateTime y utilizar esto en vez de DateTime lo largo de mi código. Esto implicaría un poco de una nueva escritura de mi base de código actual.

2) Genere dinámicamente un conjunto de datos cada vez que se ejecuta la prueba/suite.

Entonces la pregunta es: ¿es posible anular el comportamiento de DateTimes para proporcionar siempre un horario específico cuando se solicite?

+0

usted no aceptó una respuesta aún. ¿Puedes aclarar qué es lo que estás buscando en una respuesta y por qué las respuestas dadas no te satisfacen? – Gordon

+0

Tenía exactamente el mismo problema, la extensión php timecop de la respuesta de @shouze funcionaba como un amuleto. –

Respuesta

15

Debería insertar los métodos DateTime que necesita en sus pruebas para devolver los valores esperados.

$stub = $this->getMock('DateTime'); 
    $stub->expects($this->any()) 
     ->method('theMethodYouNeedToReturnACertainValue') 
     ->will($this->returnValue('your certain value')); 

Ver http://www.phpunit.de/manual/3.6/en/test-doubles.html

Si no puede stub los métodos, ya que están codificados en el código, echar un vistazo a

que explica cómo invocar una devolución de llamada cuando se invoca new. Luego puede reemplazar la clase DateTime con una clase personalizada DateTime que tenga un horario fijo. Otra opción sería utilizar http://antecedent.github.io/patchwork

+0

Gracias Gordon - La dependencia de DateTime está codificada en la mayoría de mi código. Cometí el error de usarlo como un primitivo. Todas las demás dependencias se inyectan, por lo que son fáciles de burlar. Prefiero no usar una extensión para simular, ya que esto reduce la portabilidad del código. ¡Aunque puede ser la única opción! Gracias por tu respuesta. –

2

Añadiendo a lo que ya se ha señalado @Gordon hay uno, en lugar hacker, la forma de código de prueba que se basa en la hora actual:

Mi bocetos fuera sólo un método protegido que recibe el valor "global" que puede obtener con respecto a la necesidad de crear una Clase usted mismo que puede pedir cosas como la hora actual (que sería más limpia pero en php es discutible/comprensible que las personas no quieran hacerlo) ese).

Eso sería algo como esto:

<?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() 
     ); 
    } 
} 
6

También puede utilizar el lib viajero del tiempo que utiliza AOP extensión php pecl para traer cosas similares al rubí mono parches https://github.com/rezzza/TimeTraveler

Hay también esta extensión php , inspirado de ruby ​​timecop one: https://github.com/hnw/php-timecop

+0

por favor agregue tales soluciones como comentarios debajo de la pregunta en sí ... – Lal

+0

Me gustaría ... pero no tengo el privilegio de hacerlo ATM, registro nuevo;) – shouze

+0

TimeTraveler la última vez que vi estaba roto. La alternativa timecop es mucho mejor. –

0

Puede cambiar su implementación para instanciar DateTime() explícitamente con time():

new \DateTime("@".time()); 

Esto no cambia el comportamiento de su clase.Pero ahora puede hacerlo mock time() proporcionando una función de espacio de nombres:

namespace foo; 
function time() { 
    return 123; 
} 

También es posible usar mi paquete php-mock/php-mock-phpunit para hacerlo:

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()); 
    } 
} 
0

Como estoy usando Symfony's WebTestCase para realizar pruebas funcionales utilizando las pruebas PHPUnit paquete, rápidamente no fue práctico simular todos los usos de la clase DateTime.

quería probar la aplicación, ya que maneja las solicitudes a través del tiempo, tales como galletas o las pruebas de caducidad de la caché, etc.

La mejor manera que he encontrado para hacer esto es poner en práctica mi propia clase DateTime que se extiende la clase predeterminada, y proporciona algunos métodos estáticos para permitir que se agregue/resta un sesgo de tiempo predeterminado a todos los objetos DateTime que se crean a partir de ese punto en adelante.

Esta es una característica realmente fácil de implementar y no requiere la instalación de bibliotecas personalizadas.

caveat advertencia: está (o cualquier marco que está utilizando) no va a usar la biblioteca, por lo que cualquier comportamiento que se espera el marco Symfony El único inconveniente de este método para manejar en sí, tales como memoria caché interna/galletas vencimientos, no se verán afectados por estos cambios.

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'); 
    } 

} 

El uso de esto es simple:

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 
    } 
} 
Cuestiones relacionadas