You are here

abstract class RulesIntegrationTestBase in Rules 8.3

Base class for Rules integration tests.

Rules integration tests leverage the services (plugin managers) of the Rules module to test the integration of an action or condition. Dependencies on other 3rd party modules or APIs can and should be mocked; e.g. the action to delete an entity would mock the call to the entity API.

Hierarchy

Expanded class hierarchy of RulesIntegrationTestBase

29 files declare their use of RulesIntegrationTestBase
AnnotationProcessingTest.php in tests/src/Unit/Integration/Engine/AnnotationProcessingTest.php
BanIpTest.php in tests/src/Unit/Integration/RulesAction/BanIpTest.php
ConditionAccessTest.php in tests/src/Unit/Integration/Condition/ConditionAccessTest.php
ConditionManagerTest.php in tests/src/Unit/Integration/Condition/ConditionManagerTest.php
DataCalculateValueTest.php in tests/src/Unit/Integration/RulesAction/DataCalculateValueTest.php

... See full list

File

tests/src/Unit/Integration/RulesIntegrationTestBase.php, line 37

Namespace

Drupal\Tests\rules\Unit\Integration
View source
abstract class RulesIntegrationTestBase extends UnitTestCase {

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityTypeManager;

  /**
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityFieldManager;

  /**
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityTypeBundleInfo;

  /**
   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
   */
  protected $typedDataManager;

  /**
   * @var \Drupal\rules\Core\RulesActionManagerInterface
   */
  protected $actionManager;

  /**
   * @var \Drupal\rules\Core\ConditionManager
   */
  protected $conditionManager;

  /**
   * @var \Drupal\rules\Engine\ExpressionManager
   */
  protected $rulesExpressionManager;

  /**
   * @var \Drupal\rules\Context\DataProcessorManager
   */
  protected $rulesDataProcessorManager;

  /**
   * A mocked Rules logger.channel.rules_debug service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $logger;

  /**
   * All setup'ed namespaces.
   *
   * @var \ArrayObject
   */
  protected $namespaces;

  /**
   * @var \Drupal\Core\Cache\NullBackend
   */
  protected $cacheBackend;

  /**
   * @var \Drupal\Core\Extension\ModuleHandlerInterface||\Prophecy\Prophecy\ProphecyInterface
   */
  protected $moduleHandler;

  /**
   * Array object keyed with module names and TRUE as value.
   *
   * @var \ArrayObject
   */
  protected $enabledModules;

  /**
   * The Drupal service container.
   *
   * @var \Drupal\Core\DependencyInjection\Container
   */
  protected $container;

  /**
   * The class resolver mock for the typed data manager.
   *
   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $classResolver;

  /**
   * The data fetcher service.
   *
   * @var \Drupal\typed_data\DataFetcher
   */
  protected $dataFetcher;

  /**
   * The placeholder resolver service.
   *
   * @var \Drupal\typed_data\PlaceholderResolver
   */
  protected $placeholderResolver;

  /**
   * The data filter manager.
   *
   * @var \Drupal\typed_data\DataFilterManager
   */
  protected $dataFilterManager;

  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $container = new ContainerBuilder();

    // Register plugin managers used by Rules, but mock some unwanted
    // dependencies requiring more stuff to loaded.
    $this->moduleHandler = $this
      ->prophesize(ModuleHandlerInterface::class);

    // Set all the modules as being existent.
    $this->enabledModules = new \ArrayObject();
    $this->enabledModules['rules'] = TRUE;
    $this->enabledModules['rules_test'] = TRUE;
    $enabled_modules = $this->enabledModules;
    $this->moduleHandler
      ->moduleExists(Argument::type('string'))
      ->will(function ($arguments) use ($enabled_modules) {
      if (isset($enabled_modules[$arguments[0]])) {
        return [
          $arguments[0],
          $enabled_modules[$arguments[0]],
        ];
      }

      // Handle case where a plugin provider module is not enabled.
      return [
        $arguments[0],
        FALSE,
      ];
    });

    // We don't care about alter() calls on the module handler.
    $this->moduleHandler
      ->alter(Argument::any(), Argument::any(), Argument::any(), Argument::any())
      ->willReturn(NULL);
    $this->cacheBackend = new NullBackend('rules');
    $rules_directory = __DIR__ . '/../../../..';
    $this->namespaces = new \ArrayObject([
      'Drupal\\rules' => $rules_directory . '/src',
      'Drupal\\rules_test' => $rules_directory . '/tests/modules/rules_test/src',
      'Drupal\\Core\\TypedData' => $this->root . '/core/lib/Drupal/Core/TypedData',
      'Drupal\\Core\\Validation' => $this->root . '/core/lib/Drupal/Core/Validation',
    ]);
    $this->actionManager = new RulesActionManager($this->namespaces, $this->cacheBackend, $this->moduleHandler
      ->reveal());
    $this->conditionManager = new ConditionManager($this->namespaces, $this->cacheBackend, $this->moduleHandler
      ->reveal());
    $uuid_service = new Php();
    $this->rulesExpressionManager = new ExpressionManager($this->namespaces, $this->moduleHandler
      ->reveal(), $uuid_service);
    $this->classResolver = $this
      ->prophesize(ClassResolverInterface::class);
    $this->typedDataManager = new TypedDataManager($this->namespaces, $this->cacheBackend, $this->moduleHandler
      ->reveal(), $this->classResolver
      ->reveal());
    $this->rulesDataProcessorManager = new DataProcessorManager($this->namespaces, $this->moduleHandler
      ->reveal());
    $this->entityTypeManager = $this
      ->prophesize(EntityTypeManagerInterface::class);
    $this->entityTypeManager
      ->getDefinitions()
      ->willReturn([]);

    // Setup a rules_component storage mock which returns nothing by default.
    $storage = $this
      ->prophesize(ConfigEntityStorageInterface::class);
    $storage
      ->loadMultiple(NULL)
      ->willReturn([]);
    $this->entityTypeManager
      ->getStorage('rules_component')
      ->willReturn($storage
      ->reveal());
    $this->entityFieldManager = $this
      ->prophesize(EntityFieldManagerInterface::class);
    $this->entityFieldManager
      ->getBaseFieldDefinitions()
      ->willReturn([]);
    $this->entityTypeBundleInfo = $this
      ->prophesize(EntityTypeBundleInfoInterface::class);
    $this->entityTypeBundleInfo
      ->getBundleInfo()
      ->willReturn([]);
    $this->dataFetcher = new DataFetcher();
    $this->messenger = new TestMessenger();
    $this->dataFilterManager = new DataFilterManager($this->namespaces, $this->cacheBackend, $this->moduleHandler
      ->reveal());
    $this->placeholderResolver = new PlaceholderResolver($this->dataFetcher, $this->dataFilterManager);

    // Mock the Rules debug logger service and make it return our mocked logger.
    $this->logger = $this
      ->prophesize(LoggerChannelInterface::class);
    $container
      ->set('entity_type.manager', $this->entityTypeManager
      ->reveal());
    $container
      ->set('entity_field.manager', $this->entityFieldManager
      ->reveal());
    $container
      ->set('entity_type.bundle.info', $this->entityTypeBundleInfo
      ->reveal());
    $container
      ->set('context.repository', new LazyContextRepository($container, []));
    $container
      ->set('logger.channel.rules_debug', $this->logger
      ->reveal());
    $container
      ->set('plugin.manager.rules_action', $this->actionManager);
    $container
      ->set('plugin.manager.condition', $this->conditionManager);
    $container
      ->set('plugin.manager.rules_expression', $this->rulesExpressionManager);
    $container
      ->set('plugin.manager.rules_data_processor', $this->rulesDataProcessorManager);
    $container
      ->set('messenger', $this->messenger);
    $container
      ->set('typed_data_manager', $this->typedDataManager);
    $container
      ->set('string_translation', $this
      ->getStringTranslationStub());
    $container
      ->set('uuid', $uuid_service);
    $container
      ->set('typed_data.data_fetcher', $this->dataFetcher);
    $container
      ->set('typed_data.placeholder_resolver', $this->placeholderResolver);
    \Drupal::setContainer($container);
    $this->container = $container;
  }

  /**
   * Fakes the enabling of a module and adds its namespace for plugin loading.
   *
   * This method allows plugins provided by a module to be discoverable.
   *
   * @param string $name
   *   The name of the module that's going to be enabled.
   * @param array $namespaces
   *   Map of the association between module's namespaces and filesystem paths.
   */
  protected function enableModule($name, array $namespaces = []) {
    $this->enabledModules[$name] = TRUE;
    if (empty($namespaces)) {
      $namespaces = [
        'Drupal\\' . $name => $this->root . '/' . $this
          ->constructModulePath($name) . '/src',
      ];
    }
    foreach ($namespaces as $namespace => $path) {
      $this->namespaces[$namespace] = $path;
    }
  }

  /**
   * Determines the path to a module's class files.
   *
   * Core modules and contributed modules are located in different places, and
   * the testbot (DrupalCI) does not use same directory structure as most live
   * Drupal sites, so we must discover the path instead of hardwiring it.
   *
   * This method discovers modules the same way as Drupal core, so it should
   * work for core and contributed modules in all environments.
   *
   * @see \Drupal\Core\Extension\ExtensionDiscovery
   */
  protected function constructModulePath($module) {

    // Use Unix paths regardless of platform, skip dot directories, follow
    // symlinks (to allow extensions to be linked from elsewhere), and return
    // the RecursiveDirectoryIterator instance to have access to getSubPath(),
    // since SplFileInfo does not support relative paths.
    $flags = \FilesystemIterator::UNIX_PATHS;
    $flags |= \FilesystemIterator::SKIP_DOTS;
    $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
    $flags |= \FilesystemIterator::CURRENT_AS_SELF;
    $directory_iterator = new \RecursiveDirectoryIterator($this->root, $flags);

    // Filter the recursive scan to discover extensions only.
    // Important: Without a RecursiveFilterIterator, RecursiveDirectoryIterator
    // would recurse into the entire filesystem directory tree without any kind
    // of limitations.
    $filter = new RecursiveExtensionFilterIterator($directory_iterator);

    // Ensure we find testing modules too!
    $filter
      ->acceptTests(TRUE);

    // The actual recursive filesystem scan is only invoked by instantiating the
    // RecursiveIteratorIterator.
    $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD);
    $info_files = new \RegexIterator($iterator, "/^{$module}.info.yml\$/");
    foreach ($info_files as $file) {

      // There should only be one match.
      return $file
        ->getSubPath();
    }
  }

  /**
   * Returns a typed data object.
   *
   * This helper for quick creation of typed data objects.
   *
   * @param string $data_type
   *   The data type to create an object for.
   * @param mixed $value
   *   The value to set.
   *
   * @return \Drupal\Core\TypedData\TypedDataInterface
   *   The created object.
   */
  protected function getTypedData($data_type, $value) {
    $definition = $this->typedDataManager
      ->createDataDefinition($data_type);
    $data = $this->typedDataManager
      ->create($definition);
    $data
      ->setValue($value);
    return $data;
  }

  /**
   * Helper method to mock irrelevant cache methods on entities.
   *
   * @param string $interface
   *   The interface that should be mocked, example: EntityInterface::class.
   *
   * @return \Drupal\Core\Entity\EntityInterface|\Prophecy\Prophecy\ProphecyInterface
   *   The mocked entity.
   */
  protected function prophesizeEntity($interface) {
    $entity = $this
      ->prophesize($interface);

    // Cache methods are irrelevant for the tests but might be called.
    $entity
      ->getCacheContexts()
      ->willReturn([]);
    $entity
      ->getCacheTags()
      ->willReturn([]);
    $entity
      ->getCacheMaxAge()
      ->willReturn(0);
    return $entity;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
PhpunitCompatibilityTrait::getMock Deprecated public function Returns a mock object for the specified class using the available method.
PhpunitCompatibilityTrait::setExpectedException Deprecated public function Compatibility layer for PHPUnit 6 to support PHPUnit 4 code.
RulesIntegrationTestBase::$actionManager protected property
RulesIntegrationTestBase::$cacheBackend protected property
RulesIntegrationTestBase::$classResolver protected property The class resolver mock for the typed data manager.
RulesIntegrationTestBase::$conditionManager protected property
RulesIntegrationTestBase::$container protected property The Drupal service container.
RulesIntegrationTestBase::$dataFetcher protected property The data fetcher service.
RulesIntegrationTestBase::$dataFilterManager protected property The data filter manager.
RulesIntegrationTestBase::$enabledModules protected property Array object keyed with module names and TRUE as value.
RulesIntegrationTestBase::$entityFieldManager protected property
RulesIntegrationTestBase::$entityTypeBundleInfo protected property
RulesIntegrationTestBase::$entityTypeManager protected property
RulesIntegrationTestBase::$logger protected property A mocked Rules logger.channel.rules_debug service. 6
RulesIntegrationTestBase::$messenger protected property The messenger service.
RulesIntegrationTestBase::$moduleHandler protected property
RulesIntegrationTestBase::$namespaces protected property All setup'ed namespaces.
RulesIntegrationTestBase::$placeholderResolver protected property The placeholder resolver service.
RulesIntegrationTestBase::$rulesDataProcessorManager protected property
RulesIntegrationTestBase::$rulesExpressionManager protected property
RulesIntegrationTestBase::$typedDataManager protected property
RulesIntegrationTestBase::constructModulePath protected function Determines the path to a module's class files.
RulesIntegrationTestBase::enableModule protected function Fakes the enabling of a module and adds its namespace for plugin loading.
RulesIntegrationTestBase::getTypedData protected function Returns a typed data object.
RulesIntegrationTestBase::prophesizeEntity protected function Helper method to mock irrelevant cache methods on entities.
RulesIntegrationTestBase::setUp protected function Overrides UnitTestCase::setUp 22
UnitTestCase::$randomGenerator protected property The random generator.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::assertArrayEquals protected function Asserts if two arrays are equal by sorting them first.
UnitTestCase::getBlockMockWithMachineName Deprecated protected function Mocks a block with a block plugin. 1
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.