Time problems are hard. Testing them shouldn't have to be. Recently I had a set of requirements that came with a set of complex time related conditions. There were a total of 27 different outcomes based on a set of inputs, the current time being one of them.
The architecture of the Drupal project can help make testing time easier. Time itself is a service that can be simplified and put to work for the developer.
Controlling Time
To override the time service, a Service Provider can be used.
<?php
namespace Drupal\some_module;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;
/**
* Overrides the time service with a custom implementation.
*/
class SomeModuleServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
if ($container->hasDefinition('datetime.time')) {
$definition = $container->getDefinition('datetime.time');
$definition->setClass('Drupal\some_module\Time');
}
}
}
The new service can redefine how time works. For example, the normally dynamic nature of time be brought to a complete stop. It can also be conveniently controlled.
<?php
namespace Drupal\some_module;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Overrides the core time service and allows for time to be manipulated.
*/
class Time extends \Drupal\Component\Datetime\Time {
use MessengerTrait;
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public function getCurrentTime() {
$current_date = parent::getCurrentTime();
$query = $this->requestStack->getCurrentRequest()->query;
if ($query->has('current_date')) {
$override_str = $query->get('current_date');
if ($override = strtotime($override_str)) {
$current_date = $override;
$this->messenger()->addStatus('The current time has been overridden');
}
else {
$this->messenger()->addWarning('"@date" does not match the required format, YYYY-MM-DD.', ['@date' => $override_str]);
}
}
return $current_date;
}
}
With this service in place, the current date can be easily manipulated via a query parameter. Of course, this isn't something something that one would want to do in production, but for automated functional tests this can be a lifesaver.