You are here

class CronTest in Drupal 9

Tests the Cron class.

@group Cron @coversDefaultClass \Drupal\Core\Cron

Hierarchy

Expanded class hierarchy of CronTest

File

core/tests/Drupal/Tests/Core/CronTest.php, line 24

Namespace

Drupal\Tests\Core
View source
class CronTest extends UnitTestCase {
  const REQUEUE_COUNT = 3;

  /**
   * Define the duration of each item claim for this test.
   *
   * @var int
   */
  protected $claimTime = 300;

  /**
   * An instance of the Cron class for testing.
   *
   * @var \Drupal\Core\Cron
   */
  protected $cron;

  /**
   * The queue used to store test work items.
   *
   * @var \Drupal\Core\Queue\QueueInterface
   */
  protected $queue;

  /**
   * The current state of the test in memory.
   *
   * @var \Drupal\Core\State\State
   */
  protected $state;

  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();

    // Construct a state object used for testing logger assertions.
    $this->state = new State(new KeyValueMemoryFactory());

    // Create a mock logger to set a flag in the resulting state.
    $logger = $this
      ->prophesize('Drupal\\Core\\Logger\\LoggerChannelInterface');

    // Safely ignore the cron re-run message when failing to acquire a lock.
    //
    // We don't need to run regular cron tasks, and we're still implicitly
    // testing that queues are being processed.
    //
    // This argument will need to be updated to match the message text in
    // Drupal\Core\Cron::run() should the original text ever be updated.
    $logger
      ->warning(Argument::exact('Attempting to re-run cron while it is already running.'))
      ->shouldBeCalled();

    // Set a flag to track when a message is logged by adding a callback
    // function for each logging method.
    foreach (get_class_methods(LoggerInterface::class) as $logger_method) {
      $logger
        ->{$logger_method}(Argument::cetera())
        ->will(function () {
        \Drupal::state()
          ->set('cron_test.message_logged', TRUE);
      });
    }

    // Create a logger factory to produce the resulting logger.
    $logger_factory = $this
      ->prophesize('Drupal\\Core\\Logger\\LoggerChannelFactoryInterface');
    $logger_factory
      ->get(Argument::exact('cron'))
      ->willReturn($logger
      ->reveal());

    // Create a mock time service.
    $time = $this
      ->prophesize('Drupal\\Component\\Datetime\\TimeInterface');

    // Build the container using the resulting mock objects.
    \Drupal::setContainer(new ContainerBuilder());
    \Drupal::getContainer()
      ->set('logger.factory', $logger_factory
      ->reveal());
    \Drupal::getContainer()
      ->set('datetime.time', $time
      ->reveal());
    \Drupal::getContainer()
      ->set('state', $this->state);

    // Create mock objects for constructing the Cron class.
    $module_handler = $this
      ->prophesize('Drupal\\Core\\Extension\\ModuleHandlerInterface');
    $queue_factory = $this
      ->prophesize('Drupal\\Core\\Queue\\QueueFactory');
    $queue_worker_manager = $this
      ->prophesize('Drupal\\Core\\Queue\\QueueWorkerManagerInterface');
    $state = $this
      ->prophesize('Drupal\\Core\\State\\StateInterface');
    $account_switcher = $this
      ->prophesize('Drupal\\Core\\Session\\AccountSwitcherInterface');

    // Create a lock that will always fail when attempting to acquire; we're
    // only interested in testing ::processQueues(), not the other stuff.
    $lock_backend = $this
      ->prophesize('Drupal\\Core\\Lock\\LockBackendInterface');
    $lock_backend
      ->acquire(Argument::exact('cron'), Argument::cetera())
      ->willReturn(FALSE);

    // Create a queue worker definition for testing purposes.
    $queue_worker = $this
      ->randomMachineName();
    $queue_worker_definition = [
      'id' => $queue_worker,
      'cron' => [
        'time' => &$this->claimTime,
      ],
    ];

    // Create a queue instance for this queue worker.
    $this->queue = new Memory($queue_worker);
    $queue_factory
      ->get($queue_worker)
      ->willReturn($this->queue);

    // Create a mock queue worker plugin instance based on above definition.
    $queue_worker_plugin = $this
      ->prophesize('Drupal\\Core\\Queue\\QueueWorkerInterface');
    $queue_worker_plugin
      ->processItem('Complete')
      ->willReturn();
    $queue_worker_plugin
      ->processItem('Exception')
      ->willThrow(\Exception::class);
    $queue_worker_plugin
      ->processItem('DelayedRequeueException')
      ->willThrow(DelayedRequeueException::class);
    $queue_worker_plugin
      ->processItem('SuspendQueueException')
      ->willThrow(SuspendQueueException::class);

    // 'RequeueException' would normally result in an infinite loop.
    //
    // This is avoided by throwing RequeueException for the first few calls to
    // ::processItem() and then returning void. ::testRequeueException()
    // establishes sanity assertions for this case.
    $queue_worker_plugin
      ->processItem('RequeueException')
      ->will(function ($args, $mock, $method) {

      // Fetch the number of calls to this prophesied method. This value will
      // start at zero during the first call.
      $method_calls = count($mock
        ->findProphecyMethodCalls($method
        ->getMethodName(), new ArgumentsWildcard($args)));

      // Throw the expected exception on the first few calls.
      if ($method_calls < self::REQUEUE_COUNT) {
        \Drupal::state()
          ->set('cron_test.requeue_count', $method_calls + 1);
        throw new RequeueException();
      }
    });

    // Set the mock queue worker manager to return the definition/plugin.
    $queue_worker_manager
      ->getDefinitions()
      ->willReturn([
      $queue_worker => $queue_worker_definition,
    ]);
    $queue_worker_manager
      ->createInstance($queue_worker)
      ->willReturn($queue_worker_plugin
      ->reveal());

    // Construct the Cron class to test.
    $this->cron = new Cron($module_handler
      ->reveal(), $lock_backend
      ->reveal(), $queue_factory
      ->reveal(), $state
      ->reveal(), $account_switcher
      ->reveal(), $logger
      ->reveal(), $queue_worker_manager
      ->reveal(), $time
      ->reveal());
  }

  /**
   * Resets the testing state.
   */
  protected function resetTestingState() {
    $this->queue
      ->deleteQueue();
    $this->state
      ->set('cron_test.message_logged', FALSE);
    $this->state
      ->set('cron_test.requeue_count', NULL);
  }

  /**
   * Data provider for ::testProcessQueues() method.
   */
  public function processQueuesTestData() {
    return [
      [
        'Complete',
        'assertFalse',
        0,
      ],
      [
        'Exception',
        'assertTrue',
        1,
      ],
      [
        'DelayedRequeueException',
        'assertFalse',
        1,
      ],
      [
        'SuspendQueueException',
        'assertTrue',
        1,
      ],
      [
        'RequeueException',
        'assertFalse',
        0,
      ],
    ];
  }

  /**
   * Tests the ::processQueues() method.
   *
   * @covers ::processQueues
   * @dataProvider processQueuesTestData
   */
  public function testProcessQueues($item, $message_logged_assertion, $count_post_run) {
    $this
      ->resetTestingState();
    $this->queue
      ->createItem($item);
    $this
      ->assertFalse($this->state
      ->get('cron_test.message_logged'));
    $this
      ->assertEquals(1, $this->queue
      ->numberOfItems());
    $this->cron
      ->run();
    $this
      ->{$message_logged_assertion}($this->state
      ->get('cron_test.message_logged'));
    $this
      ->assertEquals($count_post_run, $this->queue
      ->numberOfItems());
  }

  /**
   * Verify that RequeueException causes an item to be processed multiple times.
   */
  public function testRequeueException() {
    $this
      ->resetTestingState();
    $this->queue
      ->createItem('RequeueException');
    $this->cron
      ->run();

    // Fetch the number of times this item was requeued.
    $actual_requeue_count = $this->state
      ->get('cron_test.requeue_count');

    // Make sure the item was requeued at least once.
    $this
      ->assertIsInt($actual_requeue_count);

    // Ensure that the actual requeue count matches the expected value.
    $this
      ->assertEquals(self::REQUEUE_COUNT, $actual_requeue_count);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CronTest::$claimTime protected property Define the duration of each item claim for this test.
CronTest::$cron protected property An instance of the Cron class for testing.
CronTest::$queue protected property The queue used to store test work items.
CronTest::$state protected property The current state of the test in memory.
CronTest::processQueuesTestData public function Data provider for ::testProcessQueues() method.
CronTest::REQUEUE_COUNT constant
CronTest::resetTestingState protected function Resets the testing state.
CronTest::setUp protected function Overrides UnitTestCase::setUp
CronTest::testProcessQueues public function Tests the ::processQueues() method.
CronTest::testRequeueException public function Verify that RequeueException causes an item to be processed multiple times.
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
UnitTestCase::$randomGenerator protected property The random generator.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::assertArrayEquals Deprecated protected function Asserts if two arrays are equal by sorting them first.
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getConfigStorageStub public function Returns a stub config storage that returns the supplied configuration.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getRandomGenerator protected function Gets the random generator for the utility methods.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::randomMachineName public function Generates a unique random string containing letters and numbers.
UnitTestCase::setUpBeforeClass public static function