ConfigEntityBaseUnitTest.php

Namespace

Drupal\Tests\Core\Config\Entity

File

core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Tests\Core\Config\Entity;

use Drupal\Component\Plugin\PluginBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
use Drupal\Core\Plugin\RemovableDependentPluginReturn;
use Drupal\Core\Test\TestKernel;
use Drupal\Tests\Core\Config\Entity\Fixtures\ConfigEntityBaseWithPluginCollections;
use Drupal\Tests\Core\Plugin\Fixtures\TestConfigurablePlugin;
use Drupal\Tests\UnitTestCase;
use Drupal\TestTools\Random;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Prophecy\Argument;

/**
 * Tests Drupal\Core\Config\Entity\ConfigEntityBase.
 */
class ConfigEntityBaseUnitTest extends UnitTestCase {
  
  /**
   * The entity under test.
   *
   * @var \Drupal\Core\Config\Entity\ConfigEntityBase|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $entity;
  
  /**
   * The entity type used for testing.
   *
   * @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $entityType;
  
  /**
   * The entity type manager used for testing.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $entityTypeManager;
  
  /**
   * The ID of the type of the entity under test.
   *
   * @var string
   */
  protected $entityTypeId;
  
  /**
   * The UUID generator used for testing.
   *
   * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $uuid;
  
  /**
   * The provider of the entity type.
   */
  const PROVIDER = 'the_provider_of_the_entity_type';
  
  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $languageManager;
  
  /**
   * The entity ID.
   *
   * @var string
   */
  protected $id;
  
  /**
   * The mocked cache backend.
   *
   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $cacheTagsInvalidator;
  
  /**
   * The mocked typed config manager.
   *
   * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $typedConfigManager;
  
  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $moduleHandler;
  
  /**
   * The theme handler.
   *
   * @var \Drupal\Core\Extension\ThemeHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $themeHandler;
  
  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $this->id = $this->randomMachineName();
    $values = [
      'id' => $this->id,
      'langcode' => 'en',
      'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
    ];
    $this->entityTypeId = $this->randomMachineName();
    $this->entityType = $this->createMock('\\Drupal\\Core\\Config\\Entity\\ConfigEntityTypeInterface');
    $this->entityType
      ->expects($this->any())
      ->method('getProvider')
      ->willReturn(static::PROVIDER);
    $this->entityType
      ->expects($this->any())
      ->method('getConfigPrefix')
      ->willReturn('test_provider.' . $this->entityTypeId);
    $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
    $this->entityTypeManager
      ->expects($this->any())
      ->method('getDefinition')
      ->with($this->entityTypeId)
      ->willReturn($this->entityType);
    $this->uuid = $this->createMock('\\Drupal\\Component\\Uuid\\UuidInterface');
    $this->languageManager = $this->createMock('\\Drupal\\Core\\Language\\LanguageManagerInterface');
    $this->languageManager
      ->expects($this->any())
      ->method('getLanguage')
      ->with('en')
      ->willReturn(new Language([
      'id' => 'en',
    ]));
    $this->cacheTagsInvalidator = $this->createMock('Drupal\\Core\\Cache\\CacheTagsInvalidatorInterface');
    $this->typedConfigManager = $this->createMock('Drupal\\Core\\Config\\TypedConfigManagerInterface');
    $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
    $this->themeHandler = $this->prophesize(ThemeHandlerInterface::class);
    $container = new ContainerBuilder();
    $container->set('entity_type.manager', $this->entityTypeManager);
    $container->set('uuid', $this->uuid);
    $container->set('language_manager', $this->languageManager);
    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
    $container->set('config.typed', $this->typedConfigManager);
    $container->set('module_handler', $this->moduleHandler
      ->reveal());
    $container->set('theme_handler', $this->themeHandler
      ->reveal());
    \Drupal::setContainer($container);
    $this->entity = $this->getMockBuilder(StubConfigEntity::class)
      ->setConstructorArgs([
      $values,
      $this->entityTypeId,
    ])
      ->onlyMethods([])
      ->getMock();
  }
  
  /**
   * Tests calculate dependencies.
   *
   * @legacy-covers ::calculateDependencies
   * @legacy-covers ::getDependencies
   */
  public function testCalculateDependencies() : void {
    // Calculating dependencies will reset the dependencies array.
    $this->entity
      ->set('dependencies', [
      'module' => [
        'node',
      ],
    ]);
    $this->assertEmpty($this->entity
      ->calculateDependencies()
      ->getDependencies());
    // Calculating dependencies will reset the dependencies array using enforced
    // dependencies.
    $this->entity
      ->set('dependencies', [
      'module' => [
        'node',
      ],
      'enforced' => [
        'module' => 'views',
      ],
    ]);
    $dependencies = $this->entity
      ->calculateDependencies()
      ->getDependencies();
    $this->assertStringContainsString('views', $dependencies['module']);
    $this->assertStringNotContainsString('node', $dependencies['module']);
  }
  
  /**
   * Tests pre save during sync.
   *
   * @legacy-covers ::preSave
   */
  public function testPreSaveDuringSync() : void {
    $this->moduleHandler
      ->moduleExists('node')
      ->willReturn(TRUE);
    $query = $this->createMock('\\Drupal\\Core\\Entity\\Query\\QueryInterface');
    $storage = $this->createMock('\\Drupal\\Core\\Config\\Entity\\ConfigEntityStorageInterface');
    $query->expects($this->any())
      ->method('execute')
      ->willReturn([]);
    $query->expects($this->any())
      ->method('condition')
      ->willReturn($query);
    $storage->expects($this->any())
      ->method('getQuery')
      ->willReturn($query);
    $storage->expects($this->any())
      ->method('loadUnchanged')
      ->willReturn($this->entity);
    // Saving an entity will not reset the dependencies array during config
    // synchronization.
    $this->entity
      ->set('dependencies', [
      'module' => [
        'node',
      ],
    ]);
    $this->entity
      ->preSave($storage);
    $this->assertEmpty($this->entity
      ->getDependencies());
    $this->entity
      ->setSyncing(TRUE);
    $this->entity
      ->set('dependencies', [
      'module' => [
        'node',
      ],
    ]);
    $this->entity
      ->preSave($storage);
    $dependencies = $this->entity
      ->getDependencies();
    $this->assertContains('node', $dependencies['module']);
  }
  
  /**
   * Tests add dependency.
   *
   * @legacy-covers ::addDependency
   */
  public function testAddDependency() : void {
    $method = new \ReflectionMethod('\\Drupal\\Core\\Config\\Entity\\ConfigEntityBase', 'addDependency');
    $method->invoke($this->entity, 'module', static::PROVIDER);
    $method->invoke($this->entity, 'module', 'core');
    $method->invoke($this->entity, 'module', 'node');
    $dependencies = $this->entity
      ->getDependencies();
    $this->assertNotContains(static::PROVIDER, $dependencies['module']);
    $this->assertNotContains('core', $dependencies['module']);
    $this->assertContains('node', $dependencies['module']);
    // Test sorting of dependencies.
    $method->invoke($this->entity, 'module', 'action');
    $dependencies = $this->entity
      ->getDependencies();
    $this->assertEquals([
      'action',
      'node',
    ], $dependencies['module']);
    // Test sorting of dependency types.
    $method->invoke($this->entity, 'entity', 'system.action.id');
    $dependencies = $this->entity
      ->getDependencies();
    $this->assertEquals([
      'entity',
      'module',
    ], array_keys($dependencies));
  }
  
  /**
   * Tests calculate dependencies with plugin collections.
   *
   * @legacy-covers ::getDependencies
   * @legacy-covers ::calculateDependencies
   */
  public function testCalculateDependenciesWithPluginCollections(array $definition, array $expected_dependencies) : void {
    $this->moduleHandler
      ->moduleExists('the_provider_of_the_entity_type')
      ->willReturn(TRUE);
    $this->moduleHandler
      ->moduleExists('test')
      ->willReturn(TRUE);
    $this->moduleHandler
      ->moduleExists('test_theme')
      ->willReturn(FALSE);
    $this->themeHandler
      ->themeExists('test_theme')
      ->willReturn(TRUE);
    $values = [];
    $this->entity = $this->getMockBuilder('\\Drupal\\Tests\\Core\\Config\\Entity\\Fixtures\\ConfigEntityBaseWithPluginCollections')
      ->setConstructorArgs([
      $values,
      $this->entityTypeId,
    ])
      ->onlyMethods([
      'getPluginCollections',
    ])
      ->getMock();
    // Create a configurable plugin that would add a dependency.
    $instance_id = $this->randomMachineName();
    $instance = new TestConfigurablePlugin([], $instance_id, $definition);
    // Create a plugin collection to contain the instance.
    $pluginCollection = $this->getMockBuilder('\\Drupal\\Core\\Plugin\\DefaultLazyPluginCollection')
      ->disableOriginalConstructor()
      ->onlyMethods([
      'get',
    ])
      ->getMock();
    $pluginCollection->expects($this->atLeastOnce())
      ->method('get')
      ->with($instance_id)
      ->willReturn($instance);
    $pluginCollection->addInstanceId($instance_id);
    // Return the mocked plugin collection.
    $this->entity
      ->expects($this->once())
      ->method('getPluginCollections')
      ->willReturn([
      $pluginCollection,
    ]);
    $this->assertEquals($expected_dependencies, $this->entity
      ->calculateDependencies()
      ->getDependencies());
  }
  
  /**
   * Data provider for testCalculateDependenciesWithPluginCollections.
   *
   * @return array
   *   An array of test cases, each containing a plugin definition and expected dependencies.
   */
  public static function providerCalculateDependenciesWithPluginCollections() : array {
    // Start with 'a' so that order of the dependency array is fixed.
    $instance_dependency_1 = 'a' . Random::machineName(10);
    $instance_dependency_2 = 'a' . Random::machineName(11);
    return [
      // Tests that the plugin provider is a module dependency.
[
        [
          'provider' => 'test',
        ],
        [
          'module' => [
            'test',
          ],
        ],
      ],
      // Tests that the plugin provider is a theme dependency.
[
        [
          'provider' => 'test_theme',
        ],
        [
          'theme' => [
            'test_theme',
          ],
        ],
      ],
      // Tests that a plugin that is provided by the same module as the config
      // entity is not added to the dependencies array.
[
        [
          'provider' => static::PROVIDER,
        ],
        [],
      ],
      // Tests that a config entity that has a plugin which provides config
      // dependencies in its definition has them.
[
        [
          'provider' => 'test',
          'config_dependencies' => [
            'config' => [
              $instance_dependency_1,
            ],
            'module' => [
              $instance_dependency_2,
            ],
          ],
        ],
        [
          'config' => [
            $instance_dependency_1,
          ],
          'module' => [
            $instance_dependency_2,
            'test',
          ],
        ],
      ],
    ];
  }
  
  /**
   * Test dependency removal on entities with plugin collections.
   *
   * @legacy-covers ::onDependencyRemoval
   */
  public function testOnDependencyRemovalWithPluginCollections(RemovableDependentPluginReturn $on_dependency_removal_status, array $dependencies_after_removal, bool $expectation) : void {
    $dependencies = [
      'config' => [
        'bar',
        'baz',
      ],
    ];
    // Create an entity with a plugin to test.
    $instance = $this->getMockBuilder('Drupal\\Tests\\Core\\Plugin\\Fixtures\\TestConfigurablePlugin')
      ->setConstructorArgs([
      [],
      $this->randomMachineName(),
      [
        'provider' => 'foo',
      ],
    ])
      ->onlyMethods([
      'calculateDependencies',
      'onCollectionDependencyRemoval',
    ])
      ->getMock();
    // Make sure the plugin's onCollectionDependencyRemoval() method is invoked
    // from $entity->onDependencyRemoval().
    $instance->expects($this->exactly(1))
      ->method('onCollectionDependencyRemoval')
      ->willReturnCallback(static fn(array $dependencies): RemovableDependentPluginReturn => $on_dependency_removal_status);
    // The calculateDependencies() method will be called before and after
    // onCollectionDependencyRemoval(), so determine what
    // calculateDependencies() should return based on when the call is made.
    $before_on_dependency_removal = TRUE;
    // If the plugin should be removed from the collection, then
    // the plugin's calculateDependencies() should be called only once, when the
    // entity's dependencies are calculated before the call to
    // $entity->onDependencyRemoval(). Otherwise, if the plugin is not removed,
    // the plugin's calculateDependencies() should be called before and after
    // $entity->onDependencyRemoval().
    $instance->expects($on_dependency_removal_status == RemovableDependentPluginReturn::Remove ? $this->once() : $this->exactly(2))
      ->method('calculateDependencies')
      ->willReturnCallback(static function () use (&$before_on_dependency_removal, $dependencies, $dependencies_after_removal) : array {
      return $before_on_dependency_removal ? $dependencies : $dependencies_after_removal;
    });
    // Confirm the calculated entity dependencies before removing dependencies.
    $entity = $this->getMockEntityWithPluginCollection($instance);
    $collections = $entity->getPluginCollections();
    $collection = reset($collections);
    $this->assertEquals(1, count($collection));
    $entity->calculateDependencies();
    $calculated_dependencies = $entity->getDependencies();
    $this->assertEquals($dependencies, $calculated_dependencies);
    // Confirm the calculated entity dependencies after removing dependencies.
    $before_on_dependency_removal = FALSE;
    $changed = $entity->onDependencyRemoval($dependencies);
    $entity->calculateDependencies();
    $recalculated_dependencies = $entity->getDependencies();
    // If the plugin has been removed from the collection, then the collection
    // should be empty. Otherwise, there should be one plugin instance in the
    // collection.
    $this->assertEquals($on_dependency_removal_status == RemovableDependentPluginReturn::Remove ? 0 : 1, count($collection));
    $this->assertEquals($dependencies_after_removal, $recalculated_dependencies);
    $this->assertEquals($expectation, $changed);
  }
  
  /**
   * Data provider for testOnDependencyRemovalWithPluginCollections.
   */
  public static function providerOnDependencyRemovalWithPluginCollections() : array {
    return [
      // The plugin fixes all the dependencies.
[
        // Plugin ::onCollectionDependencyRemoval() return.
RemovableDependentPluginReturn::Changed,
        // Expected dependencies after ::onCollectionDependencyRemoval().
[],
        // Expected return for ConfigEntityInterface::onDependencyRemoval().
TRUE,
      ],
      // The plugin is removed from the collection.
[
        // Plugin ::onCollectionDependencyRemoval() return.
RemovableDependentPluginReturn::Remove,
        // Expected dependencies after ::onCollectionDependencyRemoval().
[],
        // Expected return for ConfigEntityInterface::onDependencyRemoval().
TRUE,
      ],
      // The plugin does not fix any dependencies.
[
        // Plugin ::onCollectionDependencyRemoval() return.
RemovableDependentPluginReturn::Unchanged,
        // Expected dependencies after ::onCollectionDependencyRemoval().
[
          'config' => [
            'bar',
            'baz',
          ],
        ],
        // Expected return for ConfigEntityInterface::onDependencyRemoval().
FALSE,
      ],
      // The plugin partially fixes dependencies.
[
        // Plugin ::onCollectionDependencyRemoval() return.
RemovableDependentPluginReturn::Changed,
        // Expected dependencies after ::onCollectionDependencyRemoval().
[
          'config' => [
            'bar',
          ],
        ],
        // Expected return for ConfigEntityInterface::onDependencyRemoval().
TRUE,
      ],
    ];
  }
  
  /**
   * Get a mock entity with a plugin collection.
   *
   * @param \Drupal\Component\Plugin\PluginBase $plugin
   *   A plugin the entity will have a collection containing.
   *
   * @return \Drupal\Core\Config\Entity\ConfigEntityBase|\PHPUnit\Framework\MockObject\MockObject
   *   A mock entity with a plugin collection containing the given plugin.
   */
  protected function getMockEntityWithPluginCollection(PluginBase $plugin) : ConfigEntityBase|MockObject {
    $values = [];
    $entity = $this->getMockBuilder('\\Drupal\\Tests\\Core\\Config\\Entity\\Fixtures\\ConfigEntityBaseWithPluginCollections')
      ->setConstructorArgs([
      $values,
      $this->entityTypeId,
    ])
      ->onlyMethods([
      'getPluginCollections',
    ])
      ->getMock();
    // Create a plugin collection to contain the instance.
    $pluginCollection = $this->getMockBuilder('\\Drupal\\Core\\Plugin\\DefaultLazyPluginCollection')
      ->disableOriginalConstructor()
      ->onlyMethods([
      'get',
    ])
      ->getMock();
    $pluginCollection->expects($this->atLeastOnce())
      ->method('get')
      ->with($plugin->getPluginId())
      ->willReturn($plugin);
    $pluginCollection->addInstanceId($plugin->getPluginId());
    // Return the mocked plugin collection.
    $entity->expects($this->atLeastOnce())
      ->method('getPluginCollections')
      ->willReturn([
      $pluginCollection,
    ]);
    return $entity;
  }
  
  /**
   * Tests calculate dependencies with third party settings.
   *
   * @legacy-covers ::calculateDependencies
   * @legacy-covers ::getDependencies
   * @legacy-covers ::onDependencyRemoval
   */
  public function testCalculateDependenciesWithThirdPartySettings() : void {
    $this->entity = $this->getMockBuilder(StubConfigEntity::class)
      ->setConstructorArgs([
      [],
      $this->entityTypeId,
    ])
      ->onlyMethods([])
      ->getMock();
    $this->entity
      ->setThirdPartySetting('test_provider', 'test', 'test');
    $this->entity
      ->setThirdPartySetting('test_provider2', 'test', 'test');
    $this->entity
      ->setThirdPartySetting(static::PROVIDER, 'test', 'test');
    $this->assertEquals([
      'test_provider',
      'test_provider2',
    ], $this->entity
      ->calculateDependencies()
      ->getDependencies()['module']);
    $changed = $this->entity
      ->onDependencyRemoval([
      'module' => [
        'test_provider2',
      ],
    ]);
    $this->assertTrue($changed, 'Calling onDependencyRemoval with an existing third party dependency provider returns TRUE.');
    $changed = $this->entity
      ->onDependencyRemoval([
      'module' => [
        'test_provider3',
      ],
    ]);
    $this->assertFalse($changed, 'Calling onDependencyRemoval with a non-existing third party dependency provider returns FALSE.');
    $this->assertEquals([
      'test_provider',
    ], $this->entity
      ->calculateDependencies()
      ->getDependencies()['module']);
  }
  
  /**
   * Tests sleep with plugin collections.
   *
   * @legacy-covers ::__sleep
   */
  public function testSleepWithPluginCollections() : void {
    $instance_id = 'the_instance_id';
    $instance = new TestConfigurablePlugin([], $instance_id, []);
    $plugin_manager = $this->prophesize(PluginManagerInterface::class);
    $plugin_manager->createInstance($instance_id, Argument::any())
      ->willReturn($instance);
    // Also set up a container with the plugin manager so that we can assert
    // that the plugin manager itself is also not serialized.
    $container = TestKernel::setContainerWithKernel();
    $container->set('plugin.manager.foo', $plugin_manager->reveal());
    $entity_values = [
      'the_plugin_collection_config' => [
        $instance_id => [
          'id' => $instance_id,
          'foo' => 'original_value',
        ],
      ],
    ];
    $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId);
    $entity->setPluginManager($plugin_manager->reveal());
    // After creating the entity, change the plugin configuration.
    $instance->setConfiguration([
      'id' => $instance_id,
      'foo' => 'new_value',
    ]);
    // After changing the plugin configuration, the entity still has the
    // original value.
    $expected_plugin_config = [
      $instance_id => [
        'id' => $instance_id,
        'foo' => 'original_value',
      ],
    ];
    $this->assertSame($expected_plugin_config, $entity->get('the_plugin_collection_config'));
    // Ensure the plugin collection and manager is not stored.
    $vars = $entity->__sleep();
    $this->assertNotContains('pluginCollection', $vars);
    $this->assertNotContains('pluginManager', $vars);
    $this->assertSame([
      'pluginManager' => 'plugin.manager.foo',
    ], $entity->get('_serviceIds'));
    $expected_plugin_config = [
      $instance_id => [
        'id' => $instance_id,
        'foo' => 'new_value',
      ],
    ];
    // Ensure the updated values are stored in the entity.
    $this->assertSame($expected_plugin_config, $entity->get('the_plugin_collection_config'));
  }
  
  /**
   * Tests get original id.
   *
   * @legacy-covers ::setOriginalId
   * @legacy-covers ::getOriginalId
   */
  public function testGetOriginalId() : void {
    $new_id = $this->randomMachineName();
    $this->entity
      ->set('id', $new_id);
    $this->assertSame($this->id, $this->entity
      ->getOriginalId());
    $this->assertSame($this->entity, $this->entity
      ->setOriginalId($new_id));
    $this->assertSame($new_id, $this->entity
      ->getOriginalId());
    // Check that setOriginalId() does not change the entity "isNew" status.
    $this->assertFalse($this->entity
      ->isNew());
    $this->entity
      ->setOriginalId($this->randomMachineName());
    $this->assertFalse($this->entity
      ->isNew());
    $this->entity
      ->enforceIsNew();
    $this->assertTrue($this->entity
      ->isNew());
    $this->entity
      ->setOriginalId($this->randomMachineName());
    $this->assertTrue($this->entity
      ->isNew());
  }
  
  /**
   * Tests is new.
   *
   * @legacy-covers ::isNew
   */
  public function testIsNew() : void {
    $this->assertFalse($this->entity
      ->isNew());
    $this->assertSame($this->entity, $this->entity
      ->enforceIsNew());
    $this->assertTrue($this->entity
      ->isNew());
    $this->entity
      ->enforceIsNew(FALSE);
    $this->assertFalse($this->entity
      ->isNew());
  }
  
  /**
   * Tests get.
   *
   * @legacy-covers ::set
   * @legacy-covers ::get
   */
  public function testGet() : void {
    $name = 'id';
    $value = $this->randomMachineName();
    $this->assertSame($this->id, $this->entity
      ->get($name));
    $this->assertSame($this->entity, $this->entity
      ->set($name, $value));
    $this->assertSame($value, $this->entity
      ->get($name));
  }
  
  /**
   * Tests set status.
   *
   * @legacy-covers ::setStatus
   * @legacy-covers ::status
   */
  public function testSetStatus() : void {
    $this->assertTrue($this->entity
      ->status());
    $this->assertSame($this->entity, $this->entity
      ->setStatus(FALSE));
    $this->assertFalse($this->entity
      ->status());
    $this->entity
      ->setStatus(TRUE);
    $this->assertTrue($this->entity
      ->status());
  }
  
  /**
   * Tests enable.
   *
   * @legacy-covers ::enable
   */
  public function testEnable() : void {
    $this->entity
      ->setStatus(FALSE);
    $this->assertSame($this->entity, $this->entity
      ->enable());
    $this->assertTrue($this->entity
      ->status());
  }
  
  /**
   * Tests disable.
   *
   * @legacy-covers ::disable
   */
  public function testDisable() : void {
    $this->entity
      ->setStatus(TRUE);
    $this->assertSame($this->entity, $this->entity
      ->disable());
    $this->assertFalse($this->entity
      ->status());
  }
  
  /**
   * Tests is syncing.
   *
   * @legacy-covers ::setSyncing
   * @legacy-covers ::isSyncing
   */
  public function testIsSyncing() : void {
    $this->assertFalse($this->entity
      ->isSyncing());
    $this->assertSame($this->entity, $this->entity
      ->setSyncing(TRUE));
    $this->assertTrue($this->entity
      ->isSyncing());
    $this->entity
      ->setSyncing(FALSE);
    $this->assertFalse($this->entity
      ->isSyncing());
  }
  
  /**
   * Tests create duplicate.
   *
   * @legacy-covers ::createDuplicate
   */
  public function testCreateDuplicate() : void {
    $this->entityType
      ->expects($this->exactly(2))
      ->method('getKey')
      ->willReturnMap([
      [
        'id',
        'id',
      ],
      [
        'uuid',
        'uuid',
      ],
    ]);
    $this->entityType
      ->expects($this->once())
      ->method('hasKey')
      ->with('uuid')
      ->willReturn(TRUE);
    $new_uuid = '8607ef21-42bc-4913-978f-8c06207b0395';
    $this->uuid
      ->expects($this->once())
      ->method('generate')
      ->willReturn($new_uuid);
    $duplicate = $this->entity
      ->createDuplicate();
    $this->assertInstanceOf('\\Drupal\\Core\\Entity\\EntityBase', $duplicate);
    $this->assertNotSame($this->entity, $duplicate);
    $this->assertFalse($this->entity
      ->isNew());
    $this->assertTrue($duplicate->isNew());
    $this->assertNull($duplicate->id());
    $this->assertNull($duplicate->getOriginalId());
    $this->assertNotEquals($this->entity
      ->uuid(), $duplicate->uuid());
    $this->assertSame($new_uuid, $duplicate->uuid());
    $this->moduleHandler
      ->invokeAll($this->entityTypeId . '_duplicate', [
      $duplicate,
      $this->entity,
    ])
      ->shouldHaveBeenCalled();
    $this->moduleHandler
      ->invokeAll('entity_duplicate', [
      $duplicate,
      $this->entity,
    ])
      ->shouldHaveBeenCalled();
  }
  
  /**
   * Tests sort.
   *
   * @legacy-covers ::sort
   */
  public function testSort() : void {
    $this->entityType
      ->expects($this->atLeastOnce())
      ->method('getKey')
      ->with('label')
      ->willReturn('label');
    $entity_a = new SortTestConfigEntityWithWeight([
      'label' => 'foo',
    ], $this->entityTypeId);
    $entity_b = new SortTestConfigEntityWithWeight([
      'label' => 'bar',
    ], $this->entityTypeId);
    // Test sorting by label.
    $list = [
      $entity_a,
      $entity_b,
    ];
    usort($list, '\\Drupal\\Core\\Config\\Entity\\ConfigEntityBase::sort');
    $this->assertSame($entity_b, $list[0]);
    $list = [
      $entity_b,
      $entity_a,
    ];
    usort($list, '\\Drupal\\Core\\Config\\Entity\\ConfigEntityBase::sort');
    $this->assertSame($entity_b, $list[0]);
    // Test sorting by weight.
    $entity_a->weight = 0;
    $entity_b->weight = 1;
    $list = [
      $entity_b,
      $entity_a,
    ];
    usort($list, '\\Drupal\\Core\\Config\\Entity\\ConfigEntityBase::sort');
    $this->assertSame($entity_a, $list[0]);
    $list = [
      $entity_a,
      $entity_b,
    ];
    usort($list, '\\Drupal\\Core\\Config\\Entity\\ConfigEntityBase::sort');
    $this->assertSame($entity_a, $list[0]);
  }
  
  /**
   * Tests to array.
   *
   * @legacy-covers ::toArray
   */
  public function testToArray() : void {
    $this->typedConfigManager
      ->expects($this->never())
      ->method('getDefinition');
    $this->entityType
      ->expects($this->any())
      ->method('getPropertiesToExport')
      ->willReturn([
      'id' => 'configId',
      'dependencies' => 'dependencies',
    ]);
    $properties = $this->entity
      ->toArray();
    $this->assertIsArray($properties);
    $this->assertEquals([
      'configId' => $this->entity
        ->id(),
      'dependencies' => [],
    ], $properties);
  }
  
  /**
   * Tests to array id key.
   *
   * @legacy-covers ::toArray
   */
  public function testToArrayIdKey() : void {
    $entity = $this->getMockBuilder(StubConfigEntity::class)
      ->setConstructorArgs([
      [],
      $this->entityTypeId,
    ])
      ->onlyMethods([
      'id',
      'get',
    ])
      ->getMock();
    $entity->expects($this->atLeastOnce())
      ->method('id')
      ->willReturn($this->id);
    $entity->expects($this->once())
      ->method('get')
      ->with('dependencies')
      ->willReturn([]);
    $this->typedConfigManager
      ->expects($this->never())
      ->method('getDefinition');
    $this->entityType
      ->expects($this->any())
      ->method('getPropertiesToExport')
      ->willReturn([
      'id' => 'configId',
      'dependencies' => 'dependencies',
    ]);
    $this->entityType
      ->expects($this->once())
      ->method('getKey')
      ->with('id')
      ->willReturn('id');
    $properties = $entity->toArray();
    $this->assertIsArray($properties);
    $this->assertEquals([
      'configId' => $entity->id(),
      'dependencies' => [],
    ], $properties);
  }
  
  /**
   * Tests third party settings.
   *
   * @legacy-covers ::getThirdPartySetting
   * @legacy-covers ::setThirdPartySetting
   * @legacy-covers ::getThirdPartySettings
   * @legacy-covers ::unsetThirdPartySetting
   * @legacy-covers ::getThirdPartyProviders
   */
  public function testThirdPartySettings() : void {
    $key = 'test';
    $third_party = 'test_provider';
    $value = $this->getRandomGenerator()
      ->string();
    // Test getThirdPartySetting() with no settings.
    $this->assertEquals($value, $this->entity
      ->getThirdPartySetting($third_party, $key, $value));
    $this->assertNull($this->entity
      ->getThirdPartySetting($third_party, $key));
    // Test setThirdPartySetting().
    $this->entity
      ->setThirdPartySetting($third_party, $key, $value);
    $this->assertEquals($value, $this->entity
      ->getThirdPartySetting($third_party, $key));
    $this->assertEquals($value, $this->entity
      ->getThirdPartySetting($third_party, $key, $this->getRandomGenerator()
      ->string()));
    // Test getThirdPartySettings().
    $this->entity
      ->setThirdPartySetting($third_party, 'test2', 'value2');
    $this->assertEquals([
      $key => $value,
      'test2' => 'value2',
    ], $this->entity
      ->getThirdPartySettings($third_party));
    // Test getThirdPartyProviders().
    $this->entity
      ->setThirdPartySetting('test_provider2', $key, $value);
    $this->assertEquals([
      $third_party,
      'test_provider2',
    ], $this->entity
      ->getThirdPartyProviders());
    // Test unsetThirdPartyProviders().
    $this->entity
      ->unsetThirdPartySetting('test_provider2', $key);
    $this->assertEquals([
      $third_party,
    ], $this->entity
      ->getThirdPartyProviders());
  }
  
  /**
   * Tests to array schema exception.
   *
   * @legacy-covers ::toArray
   */
  public function testToArraySchemaException() : void {
    $this->entityType
      ->expects($this->any())
      ->method('getPropertiesToExport')
      ->willReturn(NULL);
    $this->entityType
      ->expects($this->any())
      ->method('getClass')
      ->willReturn("FooConfigEntity");
    $this->expectException(SchemaIncompleteException::class);
    $this->expectExceptionMessage("Entity type 'FooConfigEntity' is missing 'config_export' definition in its annotation");
    $this->entity
      ->toArray();
  }
  
  /**
   * Tests set with plugin collections.
   *
   * @legacy-covers ::set
   */
  public function testSetWithPluginCollections(bool $syncing, string $expected_value) : void {
    $instance_id = 'the_instance_id';
    $instance = new TestConfigurablePlugin([
      'foo' => 'original_value',
    ], $instance_id, []);
    $plugin_manager = $this->prophesize(PluginManagerInterface::class);
    if ($syncing) {
      $plugin_manager->createInstance(Argument::cetera())
        ->shouldNotBeCalled();
    }
    else {
      $plugin_manager->createInstance($instance_id, Argument::any())
        ->willReturn($instance);
    }
    $entity_values = [
      'the_plugin_collection_config' => [
        $instance_id => [
          'id' => $instance_id,
          'foo' => 'original_value',
        ],
      ],
    ];
    $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId);
    $entity->setSyncing($syncing);
    $entity->setPluginManager($plugin_manager->reveal());
    // After creating the entity, change the configuration using the entity.
    $entity->set('the_plugin_collection_config', [
      $instance_id => [
        'id' => $instance_id,
        'foo' => 'new_value',
      ],
    ]);
    $this->assertSame($expected_value, $instance->getConfiguration()['foo']);
  }
  
  /**
   * Tests pre save with plugin collections.
   *
   * @legacy-covers ::preSave
   */
  public function testPreSaveWithPluginCollections(bool $syncing, string $expected_value) : void {
    $instance_id = 'the_instance_id';
    $instance = new TestConfigurablePlugin([
      'foo' => 'original_value',
    ], $instance_id, [
      'provider' => 'core',
    ]);
    $plugin_manager = $this->prophesize(PluginManagerInterface::class);
    if ($syncing) {
      $plugin_manager->createInstance(Argument::cetera())
        ->shouldNotBeCalled();
    }
    else {
      $plugin_manager->createInstance($instance_id, Argument::any())
        ->willReturn($instance);
    }
    $entity_values = [
      'the_plugin_collection_config' => [
        $instance_id => [
          'id' => $instance_id,
          'foo' => 'original_value',
        ],
      ],
    ];
    $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId);
    $entity->setSyncing($syncing);
    $entity->setPluginManager($plugin_manager->reveal());
    // After creating the entity, change the plugin configuration.
    $instance->setConfiguration([
      'foo' => 'new_value',
    ]);
    $query = $this->createMock('\\Drupal\\Core\\Entity\\Query\\QueryInterface');
    $storage = $this->createMock('\\Drupal\\Core\\Config\\Entity\\ConfigEntityStorageInterface');
    $query->expects($this->any())
      ->method('execute')
      ->willReturn([]);
    $query->expects($this->any())
      ->method('condition')
      ->willReturn($query);
    $storage->expects($this->any())
      ->method('getQuery')
      ->willReturn($query);
    $storage->expects($this->any())
      ->method('loadUnchanged')
      ->willReturn($entity);
    $entity->preSave($storage);
    $this->assertSame($expected_value, $entity->get('the_plugin_collection_config')[$instance_id]['foo']);
  }
  public static function providerTestSetAndPreSaveWithPluginCollections() : array {
    return [
      'Not syncing' => [
        FALSE,
        'new_value',
      ],
      'Syncing' => [
        TRUE,
        'original_value',
      ],
    ];
  }

}

/**
 * Stub class for testing.
 */
class TestConfigEntityWithPluginCollections extends ConfigEntityBaseWithPluginCollections {
  
  /**
   * The plugin collection.
   *
   * @var \Drupal\Core\Plugin\DefaultLazyPluginCollection
   */
  protected $pluginCollection;
  
  /**
   * The plugin manager.
   *
   * @var \Drupal\Component\Plugin\PluginManagerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $pluginManager;
  
  /**
   * The configuration for the plugin collection.
   */
  protected array $the_plugin_collection_config = [];
  public function setPluginManager(PluginManagerInterface $plugin_manager) : void {
    $this->pluginManager = $plugin_manager;
  }
  
  /**
   * {@inheritdoc}
   */
  public function getPluginCollections() {
    if (!$this->pluginCollection) {
      $this->pluginCollection = new DefaultLazyPluginCollection($this->pluginManager, $this->the_plugin_collection_config);
    }
    return [
      'the_plugin_collection_config' => $this->pluginCollection,
    ];
  }

}

/**
 * Test entity class to test sorting.
 */
class SortTestConfigEntityWithWeight extends ConfigEntityBase {
  
  /**
   * The label.
   *
   * @var string
   */
  public string $label;
  
  /**
   * The weight.
   *
   * @var int
   */
  public int $weight;

}

Classes

Title Deprecated Summary
ConfigEntityBaseUnitTest Tests Drupal\Core\Config\Entity\ConfigEntityBase.
SortTestConfigEntityWithWeight Test entity class to test sorting.
TestConfigEntityWithPluginCollections Stub class for testing.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.