3 namespace Drupal\Tests\Core\Entity;
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\DependencyInjection\ContainerBuilder;
7 use Drupal\Core\Entity\ContentEntityInterface;
8 use Drupal\Core\Field\BaseFieldDefinition;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\Core\TypedData\TypedDataManagerInterface;
11 use Drupal\Tests\UnitTestCase;
12 use Drupal\Core\Language\Language;
15 * @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase
19 class ContentEntityBaseUnitTest extends UnitTestCase {
22 * The bundle of the entity under test.
29 * The entity under test.
31 * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
36 * An entity with no defined language to test.
38 * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
43 * The entity type used for testing.
45 * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
47 protected $entityType;
50 * The entity manager used for testing.
52 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
54 protected $entityManager;
57 * The type ID of the entity under test.
61 protected $entityTypeId;
64 * The typed data manager used for testing.
66 * @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit_Framework_MockObject_MockObject
68 protected $typedDataManager;
71 * The field type manager used for testing.
73 * @var \Drupal\Core\Field\FieldTypePluginManager|\PHPUnit_Framework_MockObject_MockObject
75 protected $fieldTypePluginManager;
78 * The language manager.
80 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
82 protected $languageManager;
85 * The UUID generator used for testing.
87 * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
101 * @var \Drupal\Core\Field\BaseFieldDefinition[]
103 protected $fieldDefinitions;
108 protected function setUp() {
112 'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
113 'defaultLangcode' => [LanguageInterface::LANGCODE_DEFAULT => 'en'],
115 $this->entityTypeId = $this->randomMachineName();
116 $this->bundle = $this->randomMachineName();
118 $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
119 $this->entityType->expects($this->any())
121 ->will($this->returnValue([
126 $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
127 $this->entityManager->expects($this->any())
128 ->method('getDefinition')
129 ->with($this->entityTypeId)
130 ->will($this->returnValue($this->entityType));
132 $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
134 $this->typedDataManager = $this->getMock(TypedDataManagerInterface::class);
135 $this->typedDataManager->expects($this->any())
136 ->method('getDefinition')
138 ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\EntityAdapter']));
140 $english = new Language(['id' => 'en']);
141 $not_specified = new Language(['id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE]);
142 $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
143 $this->languageManager->expects($this->any())
144 ->method('getLanguages')
145 ->will($this->returnValue(['en' => $english, LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified]));
146 $this->languageManager->expects($this->any())
147 ->method('getLanguage')
149 ->will($this->returnValue($english));
150 $this->languageManager->expects($this->any())
151 ->method('getLanguage')
152 ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
153 ->will($this->returnValue($not_specified));
155 $this->fieldTypePluginManager = $this->getMockBuilder('\Drupal\Core\Field\FieldTypePluginManager')
156 ->disableOriginalConstructor()
158 $this->fieldTypePluginManager->expects($this->any())
159 ->method('getDefaultStorageSettings')
160 ->will($this->returnValue([]));
161 $this->fieldTypePluginManager->expects($this->any())
162 ->method('getDefaultFieldSettings')
163 ->will($this->returnValue([]));
164 $this->fieldTypePluginManager->expects($this->any())
165 ->method('createFieldItemList')
166 ->will($this->returnValue($this->getMock('Drupal\Core\Field\FieldItemListInterface')));
168 $container = new ContainerBuilder();
169 $container->set('entity.manager', $this->entityManager);
170 $container->set('uuid', $this->uuid);
171 $container->set('typed_data_manager', $this->typedDataManager);
172 $container->set('language_manager', $this->languageManager);
173 $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
174 \Drupal::setContainer($container);
176 $this->fieldDefinitions = [
177 'id' => BaseFieldDefinition::create('integer'),
178 'revision_id' => BaseFieldDefinition::create('integer'),
181 $this->entityManager->expects($this->any())
182 ->method('getFieldDefinitions')
183 ->with($this->entityTypeId, $this->bundle)
184 ->will($this->returnValue($this->fieldDefinitions));
186 $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle], '', TRUE, TRUE, TRUE, ['isNew']);
187 $values['defaultLangcode'] = [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED];
188 $this->entityUnd = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle]);
192 * @covers ::isNewRevision
193 * @covers ::setNewRevision
195 public function testIsNewRevision() {
196 // Set up the entity type so that on the first call there is no revision key
197 // and on the second call there is one.
198 $this->entityType->expects($this->at(0))
201 ->will($this->returnValue(FALSE));
202 $this->entityType->expects($this->at(1))
205 ->will($this->returnValue(TRUE));
206 $this->entityType->expects($this->at(2))
209 ->will($this->returnValue(TRUE));
210 $this->entityType->expects($this->at(3))
213 ->will($this->returnValue('revision_id'));
214 $this->entityType->expects($this->at(4))
217 ->will($this->returnValue(TRUE));
218 $this->entityType->expects($this->at(5))
221 ->will($this->returnValue('revision_id'));
223 $field_item_list = $this->getMockBuilder('\Drupal\Core\Field\FieldItemList')
224 ->disableOriginalConstructor()
226 $field_item = $this->getMockBuilder('\Drupal\Core\Field\FieldItemBase')
227 ->disableOriginalConstructor()
228 ->getMockForAbstractClass();
230 $this->fieldTypePluginManager->expects($this->any())
231 ->method('createFieldItemList')
232 ->with($this->entity, 'revision_id', NULL)
233 ->will($this->returnValue($field_item_list));
235 $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item));
237 $this->assertFalse($this->entity->isNewRevision());
238 $this->assertTrue($this->entity->isNewRevision());
239 $this->entity->setNewRevision(TRUE);
240 $this->assertTrue($this->entity->isNewRevision());
244 * @covers ::setNewRevision
246 public function testSetNewRevisionException() {
247 $this->entityType->expects($this->once())
250 ->will($this->returnValue(FALSE));
251 $this->setExpectedException('LogicException', 'Entity type ' . $this->entityTypeId . ' does not support revisions.');
252 $this->entity->setNewRevision();
256 * @covers ::isDefaultRevision
258 public function testIsDefaultRevision() {
259 // The default value is TRUE.
260 $this->assertTrue($this->entity->isDefaultRevision());
261 // Change the default revision, verify that the old value is returned.
262 $this->assertTrue($this->entity->isDefaultRevision(FALSE));
263 // The last call changed the return value for this call.
264 $this->assertFalse($this->entity->isDefaultRevision());
265 // The revision for a new entity should always be the default revision.
266 $this->entity->expects($this->any())
268 ->will($this->returnValue(TRUE));
269 $this->entity->isDefaultRevision(FALSE);
270 $this->assertTrue($this->entity->isDefaultRevision());
274 * @covers ::getRevisionId
276 public function testGetRevisionId() {
277 // The default getRevisionId() implementation returns NULL.
278 $this->assertNull($this->entity->getRevisionId());
282 * @covers ::isTranslatable
284 public function testIsTranslatable() {
285 $this->entityManager->expects($this->any())
286 ->method('getBundleInfo')
287 ->with($this->entityTypeId)
288 ->will($this->returnValue([
290 'translatable' => TRUE,
293 $this->languageManager->expects($this->any())
294 ->method('isMultilingual')
295 ->will($this->returnValue(TRUE));
296 $this->assertTrue($this->entity->language()->getId() == 'en');
297 $this->assertFalse($this->entity->language()->isLocked());
298 $this->assertTrue($this->entity->isTranslatable());
300 $this->assertTrue($this->entityUnd->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED);
301 $this->assertTrue($this->entityUnd->language()->isLocked());
302 $this->assertFalse($this->entityUnd->isTranslatable());
306 * @covers ::isTranslatable
308 public function testIsTranslatableForMonolingual() {
309 $this->languageManager->expects($this->any())
310 ->method('isMultilingual')
311 ->will($this->returnValue(FALSE));
312 $this->assertFalse($this->entity->isTranslatable());
316 * @covers ::preSaveRevision
318 public function testPreSaveRevision() {
319 // This method is internal, so check for errors on calling it only.
320 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
321 $record = new \stdClass();
322 // Our mocked entity->preSaveRevision() returns NULL, so assert that.
323 $this->assertNull($this->entity->preSaveRevision($storage, $record));
329 public function testValidate() {
330 $validator = $this->getMock('\Symfony\Component\Validator\ValidatorInterface');
331 /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
332 $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
335 $non_empty_violation_list = clone $empty_violation_list;
336 $violation = $this->getMock('\Symfony\Component\Validator\ConstraintViolationInterface');
337 $non_empty_violation_list->add($violation);
338 $validator->expects($this->at(0))
340 ->with($this->entity->getTypedData())
341 ->will($this->returnValue($empty_violation_list));
342 $validator->expects($this->at(1))
344 ->with($this->entity->getTypedData())
345 ->will($this->returnValue($non_empty_violation_list));
346 $this->typedDataManager->expects($this->exactly(2))
347 ->method('getValidator')
348 ->will($this->returnValue($validator));
349 $this->assertSame(0, count($this->entity->validate()));
350 $this->assertSame(1, count($this->entity->validate()));
354 * Tests required validation.
357 * @covers ::isValidationRequired
358 * @covers ::setValidationRequired
362 public function testRequiredValidation() {
363 $validator = $this->getMock('\Symfony\Component\Validator\ValidatorInterface');
364 /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
365 $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
368 $validator->expects($this->at(0))
370 ->with($this->entity->getTypedData())
371 ->will($this->returnValue($empty_violation_list));
372 $this->typedDataManager->expects($this->any())
373 ->method('getValidator')
374 ->will($this->returnValue($validator));
376 /** @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject $storage */
377 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
378 $storage->expects($this->any())
380 ->willReturnCallback(function (ContentEntityInterface $entity) use ($storage) {
381 $entity->preSave($storage);
384 $this->entityManager->expects($this->any())
385 ->method('getStorage')
386 ->with($this->entityTypeId)
387 ->will($this->returnValue($storage));
389 // Check that entities can be saved normally when validation is not
391 $this->assertFalse($this->entity->isValidationRequired());
392 $this->entity->save();
394 // Make validation required and check that if the entity is validated, it
395 // can be saved normally.
396 $this->entity->setValidationRequired(TRUE);
397 $this->assertTrue($this->entity->isValidationRequired());
398 $this->entity->validate();
399 $this->entity->save();
401 // Check that the "validated" status is reset after saving the entity and
402 // that trying to save a non-validated entity when validation is required
403 // results in an exception.
404 $this->assertTrue($this->entity->isValidationRequired());
405 $this->setExpectedException(\LogicException::class, 'Entity validation was skipped.');
406 $this->entity->save();
412 public function testBundle() {
413 $this->assertSame($this->bundle, $this->entity->bundle());
419 public function testAccess() {
420 $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
421 $operation = $this->randomMachineName();
422 $access->expects($this->at(0))
424 ->with($this->entity, $operation)
425 ->will($this->returnValue(TRUE));
426 $access->expects($this->at(1))
428 ->with($this->entity, $operation)
429 ->will($this->returnValue(AccessResult::allowed()));
430 $access->expects($this->at(2))
431 ->method('createAccess')
432 ->will($this->returnValue(TRUE));
433 $access->expects($this->at(3))
434 ->method('createAccess')
435 ->will($this->returnValue(AccessResult::allowed()));
436 $this->entityManager->expects($this->exactly(4))
437 ->method('getAccessControlHandler')
438 ->will($this->returnValue($access));
439 $this->assertTrue($this->entity->access($operation));
440 $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation, NULL, TRUE));
441 $this->assertTrue($this->entity->access('create'));
442 $this->assertEquals(AccessResult::allowed(), $this->entity->access('create', NULL, TRUE));
448 public function testLabel() {
449 // Make a mock with one method that we use as the entity's label callback.
450 // We check that it is called, and that the entity's label is the callback's
452 $callback_label = $this->randomMachineName();
453 $callback_container = $this->getMock(get_class());
454 $callback_container->expects($this->once())
455 ->method(__FUNCTION__)
456 ->will($this->returnValue($callback_label));
457 $this->entityType->expects($this->once())
458 ->method('getLabelCallback')
459 ->will($this->returnValue([$callback_container, __FUNCTION__]));
461 $this->assertSame($callback_label, $this->entity->label());
465 * Data provider for testGet().
468 * - Expected output from get().
469 * - Field name parameter to get().
470 * - Language code for $activeLanguage.
471 * - Fields array for $fields.
473 public function providerGet() {
475 // Populated fields array.
476 ['result', 'field_name', 'langcode', ['field_name' => ['langcode' => 'result']]],
477 // Incomplete fields array.
478 ['getTranslatedField_result', 'field_name', 'langcode', ['field_name' => 'no_langcode']],
479 // Empty fields array.
480 ['getTranslatedField_result', 'field_name', 'langcode', []],
486 * @dataProvider providerGet
488 public function testGet($expected, $field_name, $active_langcode, $fields) {
489 // Mock ContentEntityBase.
490 $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
491 ->disableOriginalConstructor()
492 ->setMethods(['getTranslatedField'])
493 ->getMockForAbstractClass();
495 // Set up expectations for getTranslatedField() method. In get(),
496 // getTranslatedField() is only called if the field name and language code
497 // are not present as keys in the fields array.
498 if (isset($fields[$field_name][$active_langcode])) {
499 $mock_base->expects($this->never())
500 ->method('getTranslatedField');
503 $mock_base->expects($this->once())
504 ->method('getTranslatedField')
506 $this->equalTo($field_name),
507 $this->equalTo($active_langcode)
509 ->willReturn($expected);
512 // Poke in activeLangcode.
513 $ref_langcode = new \ReflectionProperty($mock_base, 'activeLangcode');
514 $ref_langcode->setAccessible(TRUE);
515 $ref_langcode->setValue($mock_base, $active_langcode);
518 $ref_fields = new \ReflectionProperty($mock_base, 'fields');
519 $ref_fields->setAccessible(TRUE);
520 $ref_fields->setValue($mock_base, $fields);
523 $this->assertEquals($expected, $mock_base->get($field_name));
527 * Data provider for testGetFields().
530 * - Expected output from getFields().
531 * - $include_computed value to pass to getFields().
532 * - Value to mock from all field definitions for isComputed().
533 * - Array of field names to return from mocked getFieldDefinitions(). A
534 * Drupal\Core\Field\FieldDefinitionInterface object will be mocked for
537 public function providerGetFields() {
539 [[], FALSE, FALSE, []],
540 [['field' => 'field', 'field2' => 'field2'], TRUE, FALSE, ['field', 'field2']],
541 [['field3' => 'field3'], TRUE, TRUE, ['field3']],
542 [[], FALSE, TRUE, ['field4']],
547 * @covers ::getFields
548 * @dataProvider providerGetFields
550 public function testGetFields($expected, $include_computed, $is_computed, $field_definitions) {
551 // Mock ContentEntityBase.
552 $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
553 ->disableOriginalConstructor()
554 ->setMethods(['getFieldDefinitions', 'get'])
555 ->getMockForAbstractClass();
557 // Mock field definition objects for each element of $field_definitions.
558 $mocked_field_definitions = [];
559 foreach ($field_definitions as $name) {
560 $mock_definition = $this->getMockBuilder('Drupal\Core\Field\FieldDefinitionInterface')
561 ->setMethods(['isComputed'])
562 ->getMockForAbstractClass();
563 // Set expectations for isComputed(). isComputed() gets called whenever
564 // $include_computed is FALSE, but not otherwise. It returns the value of
566 $mock_definition->expects($this->exactly(
567 $include_computed ? 0 : 1
569 ->method('isComputed')
570 ->willReturn($is_computed);
571 $mocked_field_definitions[$name] = $mock_definition;
574 // Set up expectations for getFieldDefinitions().
575 $mock_base->expects($this->once())
576 ->method('getFieldDefinitions')
577 ->willReturn($mocked_field_definitions);
579 // How many time will we call get()? Since we are rigging all defined fields
580 // to be computed based on $is_computed, then if $include_computed is FALSE,
581 // get() will never be called.
583 if ($include_computed) {
584 $get_count = count($field_definitions);
587 // Set up expectations for get(). It simply returns the name passed in.
588 $mock_base->expects($this->exactly($get_count))
590 ->willReturnArgument(0);
592 // Exercise getFields().
593 $this->assertArrayEquals(
595 $mock_base->getFields($include_computed)
602 public function testSet() {
603 // Exercise set(), check if it returns $this
606 $this->entity->set('id', 0)