3 namespace Drupal\Tests\Core\Entity;
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\DependencyInjection\ContainerBuilder;
7 use Drupal\Core\Entity\ContentEntityBase;
8 use Drupal\Core\Entity\ContentEntityInterface;
9 use Drupal\Core\Entity\EntityFieldManagerInterface;
10 use Drupal\Core\Entity\EntityManager;
11 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
12 use Drupal\Core\Entity\EntityTypeManagerInterface;
13 use Drupal\Core\Field\BaseFieldDefinition;
14 use Drupal\Core\Language\LanguageInterface;
15 use Drupal\Core\TypedData\TypedDataManagerInterface;
16 use Drupal\Tests\UnitTestCase;
17 use Drupal\Core\Language\Language;
18 use Symfony\Component\Validator\Validator\ValidatorInterface;
21 * @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase
25 class ContentEntityBaseUnitTest extends UnitTestCase {
28 * The bundle of the entity under test.
35 * The entity under test.
37 * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
42 * An entity with no defined language to test.
44 * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
49 * The entity type used for testing.
51 * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
53 protected $entityType;
56 * The entity manager used for testing.
58 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
60 protected $entityManager;
63 * The entity field manager used for testing.
65 * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit_Framework_MockObject_MockObject
67 protected $entityFieldManager;
70 * The entity type bundle manager used for testing.
72 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\PHPUnit_Framework_MockObject_MockObject
74 protected $entityTypeBundleInfo;
77 * The entity type manager used for testing.
79 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
81 protected $entityTypeManager;
84 * The type ID of the entity under test.
88 protected $entityTypeId;
91 * The typed data manager used for testing.
93 * @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit_Framework_MockObject_MockObject
95 protected $typedDataManager;
98 * The field type manager used for testing.
100 * @var \Drupal\Core\Field\FieldTypePluginManager|\PHPUnit_Framework_MockObject_MockObject
102 protected $fieldTypePluginManager;
105 * The language manager.
107 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
109 protected $languageManager;
112 * The UUID generator used for testing.
114 * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
128 * @var \Drupal\Core\Field\BaseFieldDefinition[]
130 protected $fieldDefinitions;
135 protected function setUp() {
139 'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
140 'defaultLangcode' => [LanguageInterface::LANGCODE_DEFAULT => 'en'],
142 $this->entityTypeId = $this->randomMachineName();
143 $this->bundle = $this->randomMachineName();
145 $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
146 $this->entityType->expects($this->any())
148 ->will($this->returnValue([
153 $this->entityManager = new EntityManager();
155 $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class);
156 $this->entityTypeManager->expects($this->any())
157 ->method('getDefinition')
158 ->with($this->entityTypeId)
159 ->will($this->returnValue($this->entityType));
161 $this->entityFieldManager = $this->getMock(EntityFieldManagerInterface::class);
163 $this->entityTypeBundleInfo = $this->getMock(EntityTypeBundleInfoInterface::class);
165 $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
167 $this->typedDataManager = $this->getMock(TypedDataManagerInterface::class);
168 $this->typedDataManager->expects($this->any())
169 ->method('getDefinition')
171 ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\EntityAdapter']));
173 $english = new Language(['id' => 'en']);
174 $not_specified = new Language(['id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE]);
175 $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
176 $this->languageManager->expects($this->any())
177 ->method('getLanguages')
178 ->will($this->returnValue(['en' => $english, LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified]));
179 $this->languageManager->expects($this->any())
180 ->method('getLanguage')
182 ->will($this->returnValue($english));
183 $this->languageManager->expects($this->any())
184 ->method('getLanguage')
185 ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
186 ->will($this->returnValue($not_specified));
188 $this->fieldTypePluginManager = $this->getMockBuilder('\Drupal\Core\Field\FieldTypePluginManager')
189 ->disableOriginalConstructor()
191 $this->fieldTypePluginManager->expects($this->any())
192 ->method('getDefaultStorageSettings')
193 ->will($this->returnValue([]));
194 $this->fieldTypePluginManager->expects($this->any())
195 ->method('getDefaultFieldSettings')
196 ->will($this->returnValue([]));
197 $this->fieldTypePluginManager->expects($this->any())
198 ->method('createFieldItemList')
199 ->will($this->returnValue($this->getMock('Drupal\Core\Field\FieldItemListInterface')));
201 $container = new ContainerBuilder();
202 $container->set('entity.manager', $this->entityManager);
203 $container->set('entity_field.manager', $this->entityFieldManager);
204 $container->set('entity_type.bundle.info', $this->entityTypeBundleInfo);
205 $container->set('entity_type.manager', $this->entityTypeManager);
206 $container->set('uuid', $this->uuid);
207 $container->set('typed_data_manager', $this->typedDataManager);
208 $container->set('language_manager', $this->languageManager);
209 $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
210 // Inject the container into entity.manager so it can defer to
211 // entity_type.manager and other services.
212 $this->entityManager->setContainer($container);
213 \Drupal::setContainer($container);
215 $this->fieldDefinitions = [
216 'id' => BaseFieldDefinition::create('integer'),
217 'revision_id' => BaseFieldDefinition::create('integer'),
220 $this->entityFieldManager->expects($this->any())
221 ->method('getFieldDefinitions')
222 ->with($this->entityTypeId, $this->bundle)
223 ->will($this->returnValue($this->fieldDefinitions));
225 $this->entity = $this->getMockForAbstractClass(ContentEntityBase::class, [$values, $this->entityTypeId, $this->bundle], '', TRUE, TRUE, TRUE, ['isNew']);
226 $values['defaultLangcode'] = [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED];
227 $this->entityUnd = $this->getMockForAbstractClass(ContentEntityBase::class, [$values, $this->entityTypeId, $this->bundle]);
231 * @covers ::isNewRevision
232 * @covers ::setNewRevision
234 public function testIsNewRevision() {
235 // Set up the entity type so that on the first call there is no revision key
236 // and on the second call there is one.
237 $this->entityType->expects($this->at(0))
240 ->will($this->returnValue(FALSE));
241 $this->entityType->expects($this->at(1))
244 ->will($this->returnValue(TRUE));
245 $this->entityType->expects($this->at(2))
248 ->will($this->returnValue(TRUE));
249 $this->entityType->expects($this->at(3))
252 ->will($this->returnValue('revision_id'));
253 $this->entityType->expects($this->at(4))
256 ->will($this->returnValue(TRUE));
257 $this->entityType->expects($this->at(5))
260 ->will($this->returnValue('revision_id'));
262 $field_item_list = $this->getMockBuilder('\Drupal\Core\Field\FieldItemList')
263 ->disableOriginalConstructor()
265 $field_item = $this->getMockBuilder('\Drupal\Core\Field\FieldItemBase')
266 ->disableOriginalConstructor()
267 ->getMockForAbstractClass();
269 $this->fieldTypePluginManager->expects($this->any())
270 ->method('createFieldItemList')
271 ->with($this->entity, 'revision_id', NULL)
272 ->will($this->returnValue($field_item_list));
274 $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item));
276 $this->assertFalse($this->entity->isNewRevision());
277 $this->assertTrue($this->entity->isNewRevision());
278 $this->entity->setNewRevision(TRUE);
279 $this->assertTrue($this->entity->isNewRevision());
283 * @covers ::setNewRevision
285 public function testSetNewRevisionException() {
286 $this->entityType->expects($this->once())
289 ->will($this->returnValue(FALSE));
290 $this->setExpectedException('LogicException', 'Entity type ' . $this->entityTypeId . ' does not support revisions.');
291 $this->entity->setNewRevision();
295 * @covers ::isDefaultRevision
297 public function testIsDefaultRevision() {
298 // The default value is TRUE.
299 $this->assertTrue($this->entity->isDefaultRevision());
300 // Change the default revision, verify that the old value is returned.
301 $this->assertTrue($this->entity->isDefaultRevision(FALSE));
302 // The last call changed the return value for this call.
303 $this->assertFalse($this->entity->isDefaultRevision());
304 // The revision for a new entity should always be the default revision.
305 $this->entity->expects($this->any())
307 ->will($this->returnValue(TRUE));
308 $this->entity->isDefaultRevision(FALSE);
309 $this->assertTrue($this->entity->isDefaultRevision());
313 * @covers ::getRevisionId
315 public function testGetRevisionId() {
316 // The default getRevisionId() implementation returns NULL.
317 $this->assertNull($this->entity->getRevisionId());
321 * @covers ::isTranslatable
323 public function testIsTranslatable() {
324 $this->entityTypeBundleInfo->expects($this->any())
325 ->method('getBundleInfo')
326 ->with($this->entityTypeId)
327 ->will($this->returnValue([
329 'translatable' => TRUE,
332 $this->languageManager->expects($this->any())
333 ->method('isMultilingual')
334 ->will($this->returnValue(TRUE));
335 $this->assertTrue($this->entity->language()->getId() == 'en');
336 $this->assertFalse($this->entity->language()->isLocked());
337 $this->assertTrue($this->entity->isTranslatable());
339 $this->assertTrue($this->entityUnd->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED);
340 $this->assertTrue($this->entityUnd->language()->isLocked());
341 $this->assertFalse($this->entityUnd->isTranslatable());
345 * @covers ::isTranslatable
347 public function testIsTranslatableForMonolingual() {
348 $this->languageManager->expects($this->any())
349 ->method('isMultilingual')
350 ->will($this->returnValue(FALSE));
351 $this->assertFalse($this->entity->isTranslatable());
355 * @covers ::preSaveRevision
357 public function testPreSaveRevision() {
358 // This method is internal, so check for errors on calling it only.
359 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
360 $record = new \stdClass();
361 // Our mocked entity->preSaveRevision() returns NULL, so assert that.
362 $this->assertNull($this->entity->preSaveRevision($storage, $record));
368 public function testValidate() {
369 $validator = $this->getMock(ValidatorInterface::class);
370 /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
371 $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
374 $non_empty_violation_list = clone $empty_violation_list;
375 $violation = $this->getMock('\Symfony\Component\Validator\ConstraintViolationInterface');
376 $non_empty_violation_list->add($violation);
377 $validator->expects($this->at(0))
379 ->with($this->entity->getTypedData())
380 ->will($this->returnValue($empty_violation_list));
381 $validator->expects($this->at(1))
383 ->with($this->entity->getTypedData())
384 ->will($this->returnValue($non_empty_violation_list));
385 $this->typedDataManager->expects($this->exactly(2))
386 ->method('getValidator')
387 ->will($this->returnValue($validator));
388 $this->assertSame(0, count($this->entity->validate()));
389 $this->assertSame(1, count($this->entity->validate()));
393 * Tests required validation.
396 * @covers ::isValidationRequired
397 * @covers ::setValidationRequired
401 public function testRequiredValidation() {
402 $validator = $this->getMock(ValidatorInterface::class);
403 /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
404 $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
407 $validator->expects($this->at(0))
409 ->with($this->entity->getTypedData())
410 ->will($this->returnValue($empty_violation_list));
411 $this->typedDataManager->expects($this->any())
412 ->method('getValidator')
413 ->will($this->returnValue($validator));
415 /** @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject $storage */
416 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
417 $storage->expects($this->any())
419 ->willReturnCallback(function (ContentEntityInterface $entity) use ($storage) {
420 $entity->preSave($storage);
423 $this->entityTypeManager->expects($this->any())
424 ->method('getStorage')
425 ->with($this->entityTypeId)
426 ->will($this->returnValue($storage));
428 // Check that entities can be saved normally when validation is not
430 $this->assertFalse($this->entity->isValidationRequired());
431 $this->entity->save();
433 // Make validation required and check that if the entity is validated, it
434 // can be saved normally.
435 $this->entity->setValidationRequired(TRUE);
436 $this->assertTrue($this->entity->isValidationRequired());
437 $this->entity->validate();
438 $this->entity->save();
440 // Check that the "validated" status is reset after saving the entity and
441 // that trying to save a non-validated entity when validation is required
442 // results in an exception.
443 $this->assertTrue($this->entity->isValidationRequired());
444 $this->setExpectedException(\LogicException::class, 'Entity validation was skipped.');
445 $this->entity->save();
451 public function testBundle() {
452 $this->assertSame($this->bundle, $this->entity->bundle());
458 public function testAccess() {
459 $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
460 $operation = $this->randomMachineName();
461 $access->expects($this->at(0))
463 ->with($this->entity, $operation)
464 ->will($this->returnValue(TRUE));
465 $access->expects($this->at(1))
467 ->with($this->entity, $operation)
468 ->will($this->returnValue(AccessResult::allowed()));
469 $access->expects($this->at(2))
470 ->method('createAccess')
471 ->will($this->returnValue(TRUE));
472 $access->expects($this->at(3))
473 ->method('createAccess')
474 ->will($this->returnValue(AccessResult::allowed()));
475 $this->entityTypeManager->expects($this->exactly(4))
476 ->method('getAccessControlHandler')
477 ->will($this->returnValue($access));
478 $this->assertTrue($this->entity->access($operation));
479 $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation, NULL, TRUE));
480 $this->assertTrue($this->entity->access('create'));
481 $this->assertEquals(AccessResult::allowed(), $this->entity->access('create', NULL, TRUE));
487 public function testLabel() {
488 // Make a mock with one method that we use as the entity's label callback.
489 // We check that it is called, and that the entity's label is the callback's
491 $callback_label = $this->randomMachineName();
492 $callback_container = $this->getMock(get_class());
493 $callback_container->expects($this->once())
494 ->method(__FUNCTION__)
495 ->will($this->returnValue($callback_label));
496 $this->entityType->expects($this->once())
497 ->method('getLabelCallback')
498 ->will($this->returnValue([$callback_container, __FUNCTION__]));
500 $this->assertSame($callback_label, $this->entity->label());
504 * Data provider for testGet().
507 * - Expected output from get().
508 * - Field name parameter to get().
509 * - Language code for $activeLanguage.
510 * - Fields array for $fields.
512 public function providerGet() {
514 // Populated fields array.
515 ['result', 'field_name', 'langcode', ['field_name' => ['langcode' => 'result']]],
516 // Incomplete fields array.
517 ['getTranslatedField_result', 'field_name', 'langcode', ['field_name' => 'no_langcode']],
518 // Empty fields array.
519 ['getTranslatedField_result', 'field_name', 'langcode', []],
525 * @dataProvider providerGet
527 public function testGet($expected, $field_name, $active_langcode, $fields) {
528 // Mock ContentEntityBase.
529 $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
530 ->disableOriginalConstructor()
531 ->setMethods(['getTranslatedField'])
532 ->getMockForAbstractClass();
534 // Set up expectations for getTranslatedField() method. In get(),
535 // getTranslatedField() is only called if the field name and language code
536 // are not present as keys in the fields array.
537 if (isset($fields[$field_name][$active_langcode])) {
538 $mock_base->expects($this->never())
539 ->method('getTranslatedField');
542 $mock_base->expects($this->once())
543 ->method('getTranslatedField')
545 $this->equalTo($field_name),
546 $this->equalTo($active_langcode)
548 ->willReturn($expected);
551 // Poke in activeLangcode.
552 $ref_langcode = new \ReflectionProperty($mock_base, 'activeLangcode');
553 $ref_langcode->setAccessible(TRUE);
554 $ref_langcode->setValue($mock_base, $active_langcode);
557 $ref_fields = new \ReflectionProperty($mock_base, 'fields');
558 $ref_fields->setAccessible(TRUE);
559 $ref_fields->setValue($mock_base, $fields);
562 $this->assertEquals($expected, $mock_base->get($field_name));
566 * Data provider for testGetFields().
569 * - Expected output from getFields().
570 * - $include_computed value to pass to getFields().
571 * - Value to mock from all field definitions for isComputed().
572 * - Array of field names to return from mocked getFieldDefinitions(). A
573 * Drupal\Core\Field\FieldDefinitionInterface object will be mocked for
576 public function providerGetFields() {
578 [[], FALSE, FALSE, []],
579 [['field' => 'field', 'field2' => 'field2'], TRUE, FALSE, ['field', 'field2']],
580 [['field3' => 'field3'], TRUE, TRUE, ['field3']],
581 [[], FALSE, TRUE, ['field4']],
586 * @covers ::getFields
587 * @dataProvider providerGetFields
589 public function testGetFields($expected, $include_computed, $is_computed, $field_definitions) {
590 // Mock ContentEntityBase.
591 $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
592 ->disableOriginalConstructor()
593 ->setMethods(['getFieldDefinitions', 'get'])
594 ->getMockForAbstractClass();
596 // Mock field definition objects for each element of $field_definitions.
597 $mocked_field_definitions = [];
598 foreach ($field_definitions as $name) {
599 $mock_definition = $this->getMockBuilder('Drupal\Core\Field\FieldDefinitionInterface')
600 ->setMethods(['isComputed'])
601 ->getMockForAbstractClass();
602 // Set expectations for isComputed(). isComputed() gets called whenever
603 // $include_computed is FALSE, but not otherwise. It returns the value of
605 $mock_definition->expects($this->exactly(
606 $include_computed ? 0 : 1
608 ->method('isComputed')
609 ->willReturn($is_computed);
610 $mocked_field_definitions[$name] = $mock_definition;
613 // Set up expectations for getFieldDefinitions().
614 $mock_base->expects($this->once())
615 ->method('getFieldDefinitions')
616 ->willReturn($mocked_field_definitions);
618 // How many time will we call get()? Since we are rigging all defined fields
619 // to be computed based on $is_computed, then if $include_computed is FALSE,
620 // get() will never be called.
622 if ($include_computed) {
623 $get_count = count($field_definitions);
626 // Set up expectations for get(). It simply returns the name passed in.
627 $mock_base->expects($this->exactly($get_count))
629 ->willReturnArgument(0);
631 // Exercise getFields().
632 $this->assertArrayEquals(
634 $mock_base->getFields($include_computed)
641 public function testSet() {
642 // Exercise set(), check if it returns $this
645 $this->entity->set('id', 0)