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;
13 use Symfony\Component\Validator\Validator\ValidatorInterface;
16 * @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase
20 class ContentEntityBaseUnitTest extends UnitTestCase {
23 * The bundle of the entity under test.
30 * The entity under test.
32 * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
37 * An entity with no defined language to test.
39 * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
44 * The entity type used for testing.
46 * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
48 protected $entityType;
51 * The entity manager used for testing.
53 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
55 protected $entityManager;
58 * The type ID of the entity under test.
62 protected $entityTypeId;
65 * The typed data manager used for testing.
67 * @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit_Framework_MockObject_MockObject
69 protected $typedDataManager;
72 * The field type manager used for testing.
74 * @var \Drupal\Core\Field\FieldTypePluginManager|\PHPUnit_Framework_MockObject_MockObject
76 protected $fieldTypePluginManager;
79 * The language manager.
81 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
83 protected $languageManager;
86 * The UUID generator used for testing.
88 * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
102 * @var \Drupal\Core\Field\BaseFieldDefinition[]
104 protected $fieldDefinitions;
109 protected function setUp() {
113 'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
114 'defaultLangcode' => [LanguageInterface::LANGCODE_DEFAULT => 'en'],
116 $this->entityTypeId = $this->randomMachineName();
117 $this->bundle = $this->randomMachineName();
119 $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
120 $this->entityType->expects($this->any())
122 ->will($this->returnValue([
127 $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
128 $this->entityManager->expects($this->any())
129 ->method('getDefinition')
130 ->with($this->entityTypeId)
131 ->will($this->returnValue($this->entityType));
133 $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
135 $this->typedDataManager = $this->getMock(TypedDataManagerInterface::class);
136 $this->typedDataManager->expects($this->any())
137 ->method('getDefinition')
139 ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\EntityAdapter']));
141 $english = new Language(['id' => 'en']);
142 $not_specified = new Language(['id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE]);
143 $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
144 $this->languageManager->expects($this->any())
145 ->method('getLanguages')
146 ->will($this->returnValue(['en' => $english, LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified]));
147 $this->languageManager->expects($this->any())
148 ->method('getLanguage')
150 ->will($this->returnValue($english));
151 $this->languageManager->expects($this->any())
152 ->method('getLanguage')
153 ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
154 ->will($this->returnValue($not_specified));
156 $this->fieldTypePluginManager = $this->getMockBuilder('\Drupal\Core\Field\FieldTypePluginManager')
157 ->disableOriginalConstructor()
159 $this->fieldTypePluginManager->expects($this->any())
160 ->method('getDefaultStorageSettings')
161 ->will($this->returnValue([]));
162 $this->fieldTypePluginManager->expects($this->any())
163 ->method('getDefaultFieldSettings')
164 ->will($this->returnValue([]));
165 $this->fieldTypePluginManager->expects($this->any())
166 ->method('createFieldItemList')
167 ->will($this->returnValue($this->getMock('Drupal\Core\Field\FieldItemListInterface')));
169 $container = new ContainerBuilder();
170 $container->set('entity.manager', $this->entityManager);
171 $container->set('uuid', $this->uuid);
172 $container->set('typed_data_manager', $this->typedDataManager);
173 $container->set('language_manager', $this->languageManager);
174 $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
175 \Drupal::setContainer($container);
177 $this->fieldDefinitions = [
178 'id' => BaseFieldDefinition::create('integer'),
179 'revision_id' => BaseFieldDefinition::create('integer'),
182 $this->entityManager->expects($this->any())
183 ->method('getFieldDefinitions')
184 ->with($this->entityTypeId, $this->bundle)
185 ->will($this->returnValue($this->fieldDefinitions));
187 $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle], '', TRUE, TRUE, TRUE, ['isNew']);
188 $values['defaultLangcode'] = [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED];
189 $this->entityUnd = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle]);
193 * @covers ::isNewRevision
194 * @covers ::setNewRevision
196 public function testIsNewRevision() {
197 // Set up the entity type so that on the first call there is no revision key
198 // and on the second call there is one.
199 $this->entityType->expects($this->at(0))
202 ->will($this->returnValue(FALSE));
203 $this->entityType->expects($this->at(1))
206 ->will($this->returnValue(TRUE));
207 $this->entityType->expects($this->at(2))
210 ->will($this->returnValue(TRUE));
211 $this->entityType->expects($this->at(3))
214 ->will($this->returnValue('revision_id'));
215 $this->entityType->expects($this->at(4))
218 ->will($this->returnValue(TRUE));
219 $this->entityType->expects($this->at(5))
222 ->will($this->returnValue('revision_id'));
224 $field_item_list = $this->getMockBuilder('\Drupal\Core\Field\FieldItemList')
225 ->disableOriginalConstructor()
227 $field_item = $this->getMockBuilder('\Drupal\Core\Field\FieldItemBase')
228 ->disableOriginalConstructor()
229 ->getMockForAbstractClass();
231 $this->fieldTypePluginManager->expects($this->any())
232 ->method('createFieldItemList')
233 ->with($this->entity, 'revision_id', NULL)
234 ->will($this->returnValue($field_item_list));
236 $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item));
238 $this->assertFalse($this->entity->isNewRevision());
239 $this->assertTrue($this->entity->isNewRevision());
240 $this->entity->setNewRevision(TRUE);
241 $this->assertTrue($this->entity->isNewRevision());
245 * @covers ::setNewRevision
247 public function testSetNewRevisionException() {
248 $this->entityType->expects($this->once())
251 ->will($this->returnValue(FALSE));
252 $this->setExpectedException('LogicException', 'Entity type ' . $this->entityTypeId . ' does not support revisions.');
253 $this->entity->setNewRevision();
257 * @covers ::isDefaultRevision
259 public function testIsDefaultRevision() {
260 // The default value is TRUE.
261 $this->assertTrue($this->entity->isDefaultRevision());
262 // Change the default revision, verify that the old value is returned.
263 $this->assertTrue($this->entity->isDefaultRevision(FALSE));
264 // The last call changed the return value for this call.
265 $this->assertFalse($this->entity->isDefaultRevision());
266 // The revision for a new entity should always be the default revision.
267 $this->entity->expects($this->any())
269 ->will($this->returnValue(TRUE));
270 $this->entity->isDefaultRevision(FALSE);
271 $this->assertTrue($this->entity->isDefaultRevision());
275 * @covers ::getRevisionId
277 public function testGetRevisionId() {
278 // The default getRevisionId() implementation returns NULL.
279 $this->assertNull($this->entity->getRevisionId());
283 * @covers ::isTranslatable
285 public function testIsTranslatable() {
286 $this->entityManager->expects($this->any())
287 ->method('getBundleInfo')
288 ->with($this->entityTypeId)
289 ->will($this->returnValue([
291 'translatable' => TRUE,
294 $this->languageManager->expects($this->any())
295 ->method('isMultilingual')
296 ->will($this->returnValue(TRUE));
297 $this->assertTrue($this->entity->language()->getId() == 'en');
298 $this->assertFalse($this->entity->language()->isLocked());
299 $this->assertTrue($this->entity->isTranslatable());
301 $this->assertTrue($this->entityUnd->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED);
302 $this->assertTrue($this->entityUnd->language()->isLocked());
303 $this->assertFalse($this->entityUnd->isTranslatable());
307 * @covers ::isTranslatable
309 public function testIsTranslatableForMonolingual() {
310 $this->languageManager->expects($this->any())
311 ->method('isMultilingual')
312 ->will($this->returnValue(FALSE));
313 $this->assertFalse($this->entity->isTranslatable());
317 * @covers ::preSaveRevision
319 public function testPreSaveRevision() {
320 // This method is internal, so check for errors on calling it only.
321 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
322 $record = new \stdClass();
323 // Our mocked entity->preSaveRevision() returns NULL, so assert that.
324 $this->assertNull($this->entity->preSaveRevision($storage, $record));
330 public function testValidate() {
331 $validator = $this->getMock(ValidatorInterface::class);
332 /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
333 $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
336 $non_empty_violation_list = clone $empty_violation_list;
337 $violation = $this->getMock('\Symfony\Component\Validator\ConstraintViolationInterface');
338 $non_empty_violation_list->add($violation);
339 $validator->expects($this->at(0))
341 ->with($this->entity->getTypedData())
342 ->will($this->returnValue($empty_violation_list));
343 $validator->expects($this->at(1))
345 ->with($this->entity->getTypedData())
346 ->will($this->returnValue($non_empty_violation_list));
347 $this->typedDataManager->expects($this->exactly(2))
348 ->method('getValidator')
349 ->will($this->returnValue($validator));
350 $this->assertSame(0, count($this->entity->validate()));
351 $this->assertSame(1, count($this->entity->validate()));
355 * Tests required validation.
358 * @covers ::isValidationRequired
359 * @covers ::setValidationRequired
363 public function testRequiredValidation() {
364 $validator = $this->getMock(ValidatorInterface::class);
365 /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
366 $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
369 $validator->expects($this->at(0))
371 ->with($this->entity->getTypedData())
372 ->will($this->returnValue($empty_violation_list));
373 $this->typedDataManager->expects($this->any())
374 ->method('getValidator')
375 ->will($this->returnValue($validator));
377 /** @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject $storage */
378 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
379 $storage->expects($this->any())
381 ->willReturnCallback(function (ContentEntityInterface $entity) use ($storage) {
382 $entity->preSave($storage);
385 $this->entityManager->expects($this->any())
386 ->method('getStorage')
387 ->with($this->entityTypeId)
388 ->will($this->returnValue($storage));
390 // Check that entities can be saved normally when validation is not
392 $this->assertFalse($this->entity->isValidationRequired());
393 $this->entity->save();
395 // Make validation required and check that if the entity is validated, it
396 // can be saved normally.
397 $this->entity->setValidationRequired(TRUE);
398 $this->assertTrue($this->entity->isValidationRequired());
399 $this->entity->validate();
400 $this->entity->save();
402 // Check that the "validated" status is reset after saving the entity and
403 // that trying to save a non-validated entity when validation is required
404 // results in an exception.
405 $this->assertTrue($this->entity->isValidationRequired());
406 $this->setExpectedException(\LogicException::class, 'Entity validation was skipped.');
407 $this->entity->save();
413 public function testBundle() {
414 $this->assertSame($this->bundle, $this->entity->bundle());
420 public function testAccess() {
421 $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
422 $operation = $this->randomMachineName();
423 $access->expects($this->at(0))
425 ->with($this->entity, $operation)
426 ->will($this->returnValue(TRUE));
427 $access->expects($this->at(1))
429 ->with($this->entity, $operation)
430 ->will($this->returnValue(AccessResult::allowed()));
431 $access->expects($this->at(2))
432 ->method('createAccess')
433 ->will($this->returnValue(TRUE));
434 $access->expects($this->at(3))
435 ->method('createAccess')
436 ->will($this->returnValue(AccessResult::allowed()));
437 $this->entityManager->expects($this->exactly(4))
438 ->method('getAccessControlHandler')
439 ->will($this->returnValue($access));
440 $this->assertTrue($this->entity->access($operation));
441 $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation, NULL, TRUE));
442 $this->assertTrue($this->entity->access('create'));
443 $this->assertEquals(AccessResult::allowed(), $this->entity->access('create', NULL, TRUE));
449 public function testLabel() {
450 // Make a mock with one method that we use as the entity's label callback.
451 // We check that it is called, and that the entity's label is the callback's
453 $callback_label = $this->randomMachineName();
454 $callback_container = $this->getMock(get_class());
455 $callback_container->expects($this->once())
456 ->method(__FUNCTION__)
457 ->will($this->returnValue($callback_label));
458 $this->entityType->expects($this->once())
459 ->method('getLabelCallback')
460 ->will($this->returnValue([$callback_container, __FUNCTION__]));
462 $this->assertSame($callback_label, $this->entity->label());
466 * Data provider for testGet().
469 * - Expected output from get().
470 * - Field name parameter to get().
471 * - Language code for $activeLanguage.
472 * - Fields array for $fields.
474 public function providerGet() {
476 // Populated fields array.
477 ['result', 'field_name', 'langcode', ['field_name' => ['langcode' => 'result']]],
478 // Incomplete fields array.
479 ['getTranslatedField_result', 'field_name', 'langcode', ['field_name' => 'no_langcode']],
480 // Empty fields array.
481 ['getTranslatedField_result', 'field_name', 'langcode', []],
487 * @dataProvider providerGet
489 public function testGet($expected, $field_name, $active_langcode, $fields) {
490 // Mock ContentEntityBase.
491 $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
492 ->disableOriginalConstructor()
493 ->setMethods(['getTranslatedField'])
494 ->getMockForAbstractClass();
496 // Set up expectations for getTranslatedField() method. In get(),
497 // getTranslatedField() is only called if the field name and language code
498 // are not present as keys in the fields array.
499 if (isset($fields[$field_name][$active_langcode])) {
500 $mock_base->expects($this->never())
501 ->method('getTranslatedField');
504 $mock_base->expects($this->once())
505 ->method('getTranslatedField')
507 $this->equalTo($field_name),
508 $this->equalTo($active_langcode)
510 ->willReturn($expected);
513 // Poke in activeLangcode.
514 $ref_langcode = new \ReflectionProperty($mock_base, 'activeLangcode');
515 $ref_langcode->setAccessible(TRUE);
516 $ref_langcode->setValue($mock_base, $active_langcode);
519 $ref_fields = new \ReflectionProperty($mock_base, 'fields');
520 $ref_fields->setAccessible(TRUE);
521 $ref_fields->setValue($mock_base, $fields);
524 $this->assertEquals($expected, $mock_base->get($field_name));
528 * Data provider for testGetFields().
531 * - Expected output from getFields().
532 * - $include_computed value to pass to getFields().
533 * - Value to mock from all field definitions for isComputed().
534 * - Array of field names to return from mocked getFieldDefinitions(). A
535 * Drupal\Core\Field\FieldDefinitionInterface object will be mocked for
538 public function providerGetFields() {
540 [[], FALSE, FALSE, []],
541 [['field' => 'field', 'field2' => 'field2'], TRUE, FALSE, ['field', 'field2']],
542 [['field3' => 'field3'], TRUE, TRUE, ['field3']],
543 [[], FALSE, TRUE, ['field4']],
548 * @covers ::getFields
549 * @dataProvider providerGetFields
551 public function testGetFields($expected, $include_computed, $is_computed, $field_definitions) {
552 // Mock ContentEntityBase.
553 $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
554 ->disableOriginalConstructor()
555 ->setMethods(['getFieldDefinitions', 'get'])
556 ->getMockForAbstractClass();
558 // Mock field definition objects for each element of $field_definitions.
559 $mocked_field_definitions = [];
560 foreach ($field_definitions as $name) {
561 $mock_definition = $this->getMockBuilder('Drupal\Core\Field\FieldDefinitionInterface')
562 ->setMethods(['isComputed'])
563 ->getMockForAbstractClass();
564 // Set expectations for isComputed(). isComputed() gets called whenever
565 // $include_computed is FALSE, but not otherwise. It returns the value of
567 $mock_definition->expects($this->exactly(
568 $include_computed ? 0 : 1
570 ->method('isComputed')
571 ->willReturn($is_computed);
572 $mocked_field_definitions[$name] = $mock_definition;
575 // Set up expectations for getFieldDefinitions().
576 $mock_base->expects($this->once())
577 ->method('getFieldDefinitions')
578 ->willReturn($mocked_field_definitions);
580 // How many time will we call get()? Since we are rigging all defined fields
581 // to be computed based on $is_computed, then if $include_computed is FALSE,
582 // get() will never be called.
584 if ($include_computed) {
585 $get_count = count($field_definitions);
588 // Set up expectations for get(). It simply returns the name passed in.
589 $mock_base->expects($this->exactly($get_count))
591 ->willReturnArgument(0);
593 // Exercise getFields().
594 $this->assertArrayEquals(
596 $mock_base->getFields($include_computed)
603 public function testSet() {
604 // Exercise set(), check if it returns $this
607 $this->entity->set('id', 0)