ContentEntityBaseUnitTest.php
Same filename in other branches
Namespace
Drupal\Tests\Core\EntityFile
-
core/
tests/ Drupal/ Tests/ Core/ Entity/ ContentEntityBaseUnitTest.php
View source
<?php
declare (strict_types=1);
namespace Drupal\Tests\Core\Entity;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Language\Language;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase
* @group Entity
* @group Access
*/
class ContentEntityBaseUnitTest extends UnitTestCase {
/**
* The bundle of the entity under test.
*
* @var string
*/
protected $bundle;
/**
* The entity under test.
*/
protected ContentEntityBaseMockableClass&MockObject $entity;
/**
* An entity with no defined language to test.
*/
protected ContentEntityBaseMockableClass&MockObject $entityUnd;
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityType;
/**
* The entity field manager used for testing.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityFieldManager;
/**
* The entity type bundle manager used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityTypeBundleInfo;
/**
* The entity type manager used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityTypeManager;
/**
* The type ID of the entity under test.
*
* @var string
*/
protected $entityTypeId;
/**
* The typed data manager used for testing.
*
* @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit\Framework\MockObject\MockObject
*/
protected $typedDataManager;
/**
* The field type manager used for testing.
*
* @var \Drupal\Core\Field\FieldTypePluginManager|\PHPUnit\Framework\MockObject\MockObject
*/
protected $fieldTypePluginManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $languageManager;
/**
* The UUID generator used for testing.
*
* @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $uuid;
/**
* The entity ID.
*
* @var int
*/
protected $id;
/**
* Field definitions.
*
* @var \Drupal\Core\Field\BaseFieldDefinition[]
*/
protected $fieldDefinitions;
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
$this->id = 1;
$values = [
'id' => $this->id,
'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
'defaultLangcode' => [
LanguageInterface::LANGCODE_DEFAULT => 'en',
],
];
$this->entityTypeId = $this->randomMachineName();
$this->bundle = $this->randomMachineName();
$this->entityType = $this->createMock('\\Drupal\\Core\\Entity\\EntityTypeInterface');
$this->entityType
->expects($this->any())
->method('getKeys')
->willReturn([
'id' => 'id',
'uuid' => 'uuid',
]);
$this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$this->entityTypeManager
->expects($this->any())
->method('getDefinition')
->with($this->entityTypeId)
->willReturn($this->entityType);
$this->entityFieldManager = $this->createMock(EntityFieldManagerInterface::class);
$this->entityTypeBundleInfo = $this->createMock(EntityTypeBundleInfoInterface::class);
$this->uuid = $this->createMock('\\Drupal\\Component\\Uuid\\UuidInterface');
$this->typedDataManager = $this->createMock(TypedDataManagerInterface::class);
$this->typedDataManager
->expects($this->any())
->method('getDefinition')
->willReturn([
'class' => '\\Drupal\\Core\\Entity\\Plugin\\DataType\\EntityAdapter',
]);
$english = new Language([
'id' => 'en',
]);
$not_specified = new Language([
'id' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'locked' => TRUE,
]);
$this->languageManager = $this->createMock('\\Drupal\\Core\\Language\\LanguageManagerInterface');
$this->languageManager
->expects($this->any())
->method('getLanguages')
->willReturn([
'en' => $english,
LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified,
]);
$this->languageManager
->expects($this->any())
->method('getLanguage')
->with('en')
->willReturn($english);
$this->languageManager
->expects($this->any())
->method('getLanguage')
->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
->willReturn($not_specified);
$this->fieldTypePluginManager = $this->getMockBuilder('\\Drupal\\Core\\Field\\FieldTypePluginManager')
->disableOriginalConstructor()
->getMock();
$this->fieldTypePluginManager
->expects($this->any())
->method('getDefaultStorageSettings')
->willReturn([]);
$this->fieldTypePluginManager
->expects($this->any())
->method('getDefaultFieldSettings')
->willReturn([]);
$this->fieldTypePluginManager
->expects($this->any())
->method('createFieldItemList')
->willReturn($this->createMock('Drupal\\Core\\Field\\FieldItemListInterface'));
$container = new ContainerBuilder();
$container->set('entity_field.manager', $this->entityFieldManager);
$container->set('entity_type.bundle.info', $this->entityTypeBundleInfo);
$container->set('entity_type.manager', $this->entityTypeManager);
$container->set('uuid', $this->uuid);
$container->set('typed_data_manager', $this->typedDataManager);
$container->set('language_manager', $this->languageManager);
$container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
\Drupal::setContainer($container);
$this->fieldDefinitions = [
'id' => BaseFieldDefinition::create('integer'),
'revision_id' => BaseFieldDefinition::create('integer'),
];
$this->entityFieldManager
->expects($this->any())
->method('getFieldDefinitions')
->with($this->entityTypeId, $this->bundle)
->willReturn($this->fieldDefinitions);
$this->entity = $this->getMockBuilder(ContentEntityBaseMockableClass::class)
->setConstructorArgs([
$values,
$this->entityTypeId,
$this->bundle,
])
->onlyMethods([
'isNew',
])
->getMock();
$values['defaultLangcode'] = [
LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED,
];
$this->entityUnd = $this->getMockBuilder(ContentEntityBaseMockableClass::class)
->setConstructorArgs([
$values,
$this->entityTypeId,
$this->bundle,
])
->onlyMethods([])
->getMock();
}
/**
* @covers ::isNewRevision
* @covers ::setNewRevision
*/
public function testIsNewRevision() : void {
// Set up the entity type so that on the first call there is no revision key
// and on the second call there is one.
$this->entityType
->expects($this->exactly(4))
->method('hasKey')
->with('revision')
->willReturnOnConsecutiveCalls(FALSE, TRUE, TRUE, TRUE);
$this->entityType
->expects($this->exactly(2))
->method('getKey')
->with('revision')
->willReturn('revision_id');
$field_item_list = $this->getMockBuilder('\\Drupal\\Core\\Field\\FieldItemList')
->disableOriginalConstructor()
->getMock();
$field_item = $this->getMockBuilder('\\Drupal\\Core\\Field\\FieldItemBase')
->disableOriginalConstructor()
->getMockForAbstractClass();
$this->fieldTypePluginManager
->expects($this->any())
->method('createFieldItemList')
->with($this->entity, 'revision_id', NULL)
->willReturn($field_item_list);
$this->fieldDefinitions['revision_id']
->getItemDefinition()
->setClass(get_class($field_item));
$this->assertFalse($this->entity
->isNewRevision());
$this->assertTrue($this->entity
->isNewRevision());
$this->entity
->setNewRevision(TRUE);
$this->assertTrue($this->entity
->isNewRevision());
}
/**
* @covers ::setNewRevision
*/
public function testSetNewRevisionException() : void {
$this->entityType
->expects($this->once())
->method('hasKey')
->with('revision')
->willReturn(FALSE);
$this->expectException('LogicException');
$this->expectExceptionMessage('Entity type ' . $this->entityTypeId . ' does not support revisions.');
$this->entity
->setNewRevision();
}
/**
* @covers ::isDefaultRevision
*/
public function testIsDefaultRevision() : void {
// The default value is TRUE.
$this->assertTrue($this->entity
->isDefaultRevision());
// Change the default revision, verify that the old value is returned.
$this->assertTrue($this->entity
->isDefaultRevision(FALSE));
// The last call changed the return value for this call.
$this->assertFalse($this->entity
->isDefaultRevision());
// The revision for a new entity should always be the default revision.
$this->entity
->expects($this->any())
->method('isNew')
->willReturn(TRUE);
$this->entity
->isDefaultRevision(FALSE);
$this->assertTrue($this->entity
->isDefaultRevision());
}
/**
* @covers ::getRevisionId
*/
public function testGetRevisionId() : void {
// The default getRevisionId() implementation returns NULL.
$this->assertNull($this->entity
->getRevisionId());
}
/**
* @covers ::isTranslatable
*/
public function testIsTranslatable() : void {
$this->entityTypeBundleInfo
->expects($this->any())
->method('getBundleInfo')
->with($this->entityTypeId)
->willReturn([
$this->bundle => [
'translatable' => TRUE,
],
]);
$this->languageManager
->expects($this->any())
->method('isMultilingual')
->willReturn(TRUE);
$this->assertSame('en', $this->entity
->language()
->getId());
$this->assertFalse($this->entity
->language()
->isLocked());
$this->assertTrue($this->entity
->isTranslatable());
$this->assertSame(LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->entityUnd
->language()
->getId());
$this->assertTrue($this->entityUnd
->language()
->isLocked());
$this->assertFalse($this->entityUnd
->isTranslatable());
}
/**
* @covers ::isTranslatable
*/
public function testIsTranslatableForMonolingual() : void {
$this->languageManager
->expects($this->any())
->method('isMultilingual')
->willReturn(FALSE);
$this->assertFalse($this->entity
->isTranslatable());
}
/**
* @covers ::preSaveRevision
*/
public function testPreSaveRevision() : void {
// This method is internal, so check for errors on calling it only.
$storage = $this->createMock('\\Drupal\\Core\\Entity\\EntityStorageInterface');
$record = new \stdClass();
// Our mocked entity->preSaveRevision() returns NULL, so assert that.
$this->assertNull($this->entity
->preSaveRevision($storage, $record));
}
/**
* Data provider for the ::getTypedData() test.
*
* The following entity data definitions, the first two being derivatives of
* the last definition, will be tested in order:
*
* 1. entity:$entity_type:$bundle
* 2. entity:$entity_type
* 3. entity
*
* @see \Drupal\Core\Entity\EntityBase::getTypedData()
* @see \Drupal\Core\Entity\EntityBase::getTypedDataClass()
* @see \Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver
*
* @return array
* Array of arrays with the following elements:
* - A bool whether to provide a bundle-specific definition.
* - A bool whether to provide an entity type-specific definition.
*/
public static function providerTestTypedData() : array {
return [
'Entity data definition derivative with entity type and bundle' => [
TRUE,
TRUE,
],
'Entity data definition derivative with entity type' => [
FALSE,
TRUE,
],
'Entity data definition' => [
FALSE,
FALSE,
],
];
}
/**
* Tests each condition in EntityBase::getTypedData().
*
* @covers ::getTypedData
* @dataProvider providerTestTypedData
*/
public function testTypedData(bool $bundle_typed_data_definition, bool $entity_type_typed_data_definition) : void {
$expected = EntityAdapter::class;
$typedDataManager = $this->createMock(TypedDataManagerInterface::class);
$typedDataManager->expects($this->once())
->method('getDefinition')
->willReturnMap([
[
"entity:{$this->entityTypeId}:{$this->bundle}",
FALSE,
$bundle_typed_data_definition ? [
'class' => $expected,
] : NULL,
],
[
"entity:{$this->entityTypeId}",
FALSE,
$entity_type_typed_data_definition ? [
'class' => $expected,
] : NULL,
],
[
'entity',
TRUE,
[
'class' => $expected,
],
],
]);
// Temporarily replace the appropriate services in the container.
$container = \Drupal::getContainer();
$container->set('typed_data_manager', $typedDataManager);
\Drupal::setContainer($container);
// Create a mock entity used to retrieve typed data.
$entity = $this->getMockBuilder(ContentEntityBaseMockableClass::class)
->setConstructorArgs([
[],
$this->entityTypeId,
$this->bundle,
])
->onlyMethods([
'isNew',
])
->getMock();
// Assert that the returned data type is an instance of EntityAdapter.
$this->assertInstanceOf($expected, $entity->getTypedData());
}
/**
* @covers ::validate
*/
public function testValidate() : void {
$validator = $this->createMock(ValidatorInterface::class);
/** @var \Symfony\Component\Validator\ConstraintViolationList $empty_violation_list */
$empty_violation_list = new ConstraintViolationList();
$non_empty_violation_list = clone $empty_violation_list;
$violation = $this->createMock('\\Symfony\\Component\\Validator\\ConstraintViolationInterface');
$non_empty_violation_list->add($violation);
$validator->expects($this->exactly(2))
->method('validate')
->with($this->entity
->getTypedData())
->willReturnOnConsecutiveCalls($empty_violation_list, $non_empty_violation_list);
$this->typedDataManager
->expects($this->exactly(2))
->method('getValidator')
->willReturn($validator);
$this->assertCount(0, $this->entity
->validate());
$this->assertCount(1, $this->entity
->validate());
}
/**
* Tests required validation.
*
* @covers ::validate
* @covers ::isValidationRequired
* @covers ::setValidationRequired
* @covers ::save
* @covers ::preSave
*/
public function testRequiredValidation() : void {
$validator = $this->createMock(ValidatorInterface::class);
/** @var \Symfony\Component\Validator\ConstraintViolationList $empty_violation_list */
$empty_violation_list = new ConstraintViolationList();
$validator->expects($this->once())
->method('validate')
->with($this->entity
->getTypedData())
->willReturn($empty_violation_list);
$this->typedDataManager
->expects($this->any())
->method('getValidator')
->willReturn($validator);
/** @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit\Framework\MockObject\MockObject $storage */
$storage = $this->createMock('\\Drupal\\Core\\Entity\\EntityStorageInterface');
$storage->expects($this->any())
->method('save')
->willReturnCallback(function (ContentEntityInterface $entity) use ($storage) {
$entity->preSave($storage);
});
$this->entityTypeManager
->expects($this->any())
->method('getStorage')
->with($this->entityTypeId)
->willReturn($storage);
// Check that entities can be saved normally when validation is not
// required.
$this->assertFalse($this->entity
->isValidationRequired());
$this->entity
->save();
// Make validation required and check that if the entity is validated, it
// can be saved normally.
$this->entity
->setValidationRequired(TRUE);
$this->assertTrue($this->entity
->isValidationRequired());
$this->entity
->validate();
$this->entity
->save();
// Check that the "validated" status is reset after saving the entity and
// that trying to save a non-validated entity when validation is required
// results in an exception.
$this->assertTrue($this->entity
->isValidationRequired());
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Entity validation is required, but was skipped.');
$this->entity
->save();
}
/**
* @covers ::bundle
*/
public function testBundle() : void {
$this->assertSame($this->bundle, $this->entity
->bundle());
}
/**
* @covers ::access
*/
public function testAccess() : void {
$access = $this->createMock('\\Drupal\\Core\\Entity\\EntityAccessControlHandlerInterface');
$operation = $this->randomMachineName();
$access->expects($this->exactly(2))
->method('access')
->with($this->entity, $operation)
->willReturnOnConsecutiveCalls(TRUE, AccessResult::allowed());
$access->expects($this->exactly(2))
->method('createAccess')
->willReturnOnConsecutiveCalls(TRUE, AccessResult::allowed());
$this->entityTypeManager
->expects($this->exactly(4))
->method('getAccessControlHandler')
->willReturn($access);
$this->assertTrue($this->entity
->access($operation));
$this->assertEquals(AccessResult::allowed(), $this->entity
->access($operation, NULL, TRUE));
$this->assertTrue($this->entity
->access('create'));
$this->assertEquals(AccessResult::allowed(), $this->entity
->access('create', NULL, TRUE));
}
/**
* Data provider for testGet().
*
* @return array
* - Expected output from get().
* - Field name parameter to get().
* - Language code for $activeLanguage.
* - Fields array for $fields.
*/
public static function providerGet() {
return [
// Populated fields array.
[
'result',
'field_name',
'langcode',
[
'field_name' => [
'langcode' => 'result',
],
],
],
// Incomplete fields array.
[
'getTranslatedField_result',
'field_name',
'langcode',
[
'field_name' => 'no_langcode',
],
],
// Empty fields array.
[
'getTranslatedField_result',
'field_name',
'langcode',
[],
],
];
}
/**
* @covers ::get
* @dataProvider providerGet
*/
public function testGet($expected, $field_name, $active_langcode, $fields) : void {
// Mock ContentEntityBase.
$mock_base = $this->getMockBuilder(ContentEntityBaseMockableClass::class)
->disableOriginalConstructor()
->onlyMethods([
'getTranslatedField',
])
->getMock();
// Set up expectations for getTranslatedField() method. In get(),
// getTranslatedField() is only called if the field name and language code
// are not present as keys in the fields array.
if (isset($fields[$field_name][$active_langcode])) {
$mock_base->expects($this->never())
->method('getTranslatedField');
}
else {
$mock_base->expects($this->once())
->method('getTranslatedField')
->with($this->equalTo($field_name), $this->equalTo($active_langcode))
->willReturn($expected);
}
// Poke in activeLangcode.
$ref_langcode = new \ReflectionProperty($mock_base, 'activeLangcode');
$ref_langcode->setValue($mock_base, $active_langcode);
// Poke in fields.
$ref_fields = new \ReflectionProperty($mock_base, 'fields');
$ref_fields->setValue($mock_base, $fields);
// Exercise get().
$this->assertEquals($expected, $mock_base->get($field_name));
}
/**
* Data provider for testGetFields().
*
* @return array
* - Expected output from getFields().
* - $include_computed value to pass to getFields().
* - Value to mock from all field definitions for isComputed().
* - Array of field names to return from mocked getFieldDefinitions(). A
* Drupal\Core\Field\FieldDefinitionInterface object will be mocked for
* each name.
*/
public static function providerGetFields() {
return [
[
[],
FALSE,
FALSE,
[],
],
[
[
'field' => 'field',
'field2' => 'field2',
],
TRUE,
FALSE,
[
'field',
'field2',
],
],
[
[
'field3' => 'field3',
],
TRUE,
TRUE,
[
'field3',
],
],
[
[],
FALSE,
TRUE,
[
'field4',
],
],
];
}
/**
* @covers ::getFields
* @dataProvider providerGetFields
*/
public function testGetFields($expected, $include_computed, $is_computed, $field_definitions) : void {
// Mock ContentEntityBase.
$mock_base = $this->getMockBuilder(ContentEntityBaseMockableClass::class)
->disableOriginalConstructor()
->onlyMethods([
'getFieldDefinitions',
'get',
])
->getMock();
// Mock field definition objects for each element of $field_definitions.
$mocked_field_definitions = [];
foreach ($field_definitions as $name) {
$mock_definition = $this->createMock('Drupal\\Core\\Field\\FieldDefinitionInterface');
// Set expectations for isComputed(). isComputed() gets called whenever
// $include_computed is FALSE, but not otherwise. It returns the value of
// $is_computed.
$mock_definition->expects($this->exactly($include_computed ? 0 : 1))
->method('isComputed')
->willReturn($is_computed);
$mocked_field_definitions[$name] = $mock_definition;
}
// Set up expectations for getFieldDefinitions().
$mock_base->expects($this->once())
->method('getFieldDefinitions')
->willReturn($mocked_field_definitions);
// How many time will we call get()? Since we are rigging all defined fields
// to be computed based on $is_computed, then if $include_computed is FALSE,
// get() will never be called.
$get_count = 0;
if ($include_computed) {
$get_count = count($field_definitions);
}
// Set up expectations for get(). It simply returns the name passed in.
$mock_base->expects($this->exactly($get_count))
->method('get')
->willReturnArgument(0);
// Exercise getFields().
$this->assertEquals($expected, $mock_base->getFields($include_computed));
}
/**
* @covers ::set
*/
public function testSet() : void {
// Exercise set(), check if it returns $this
$this->assertSame($this->entity, $this->entity
->set('id', 0));
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
ContentEntityBaseUnitTest | @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase @group Entity @group Access |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.