3 namespace Drupal\Tests\Core\Entity;
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\DependencyInjection\ContainerBuilder;
8 use Drupal\Core\Entity\EntityStorageInterface;
9 use Drupal\Core\Entity\EntityTypeManagerInterface;
10 use Drupal\Core\Entity\EntityTypeRepositoryInterface;
11 use Drupal\Core\Language\Language;
12 use Drupal\entity_test\Entity\EntityTestMul;
13 use Drupal\Tests\UnitTestCase;
16 * @coversDefaultClass \Drupal\Core\Entity\Entity
20 class EntityUnitTest extends UnitTestCase {
23 * The entity under test.
25 * @var \Drupal\Core\Entity\Entity|\PHPUnit_Framework_MockObject_MockObject
30 * The entity type used for testing.
32 * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
34 protected $entityType;
37 * The entity type manager used for testing.
39 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
41 protected $entityTypeManager;
44 * The ID of the type of the entity under test.
48 protected $entityTypeId;
51 * The route provider used for testing.
53 * @var \Drupal\Core\Routing\RouteProvider|\PHPUnit_Framework_MockObject_MockObject
55 protected $routeProvider;
58 * The UUID generator used for testing.
60 * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
65 * The language manager.
67 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
69 protected $languageManager;
72 * The mocked cache tags invalidator.
74 * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
76 protected $cacheTagsInvalidator;
88 protected function setUp() {
92 'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
94 $this->entityTypeId = $this->randomMachineName();
96 $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
97 $this->entityType->expects($this->any())
98 ->method('getListCacheTags')
99 ->willReturn([$this->entityTypeId . '_list']);
101 $this->entityTypeManager = $this->getMockForAbstractClass(EntityTypeManagerInterface::class);
102 $this->entityTypeManager->expects($this->any())
103 ->method('getDefinition')
104 ->with($this->entityTypeId)
105 ->will($this->returnValue($this->entityType));
107 $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
109 $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
110 $this->languageManager->expects($this->any())
111 ->method('getLanguage')
113 ->will($this->returnValue(new Language(['id' => 'en'])));
115 $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidator');
117 $container = new ContainerBuilder();
118 // Ensure that Entity doesn't use the deprecated entity.manager service.
119 $container->set('entity.manager', NULL);
120 $container->set('entity_type.manager', $this->entityTypeManager);
121 $container->set('uuid', $this->uuid);
122 $container->set('language_manager', $this->languageManager);
123 $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
124 \Drupal::setContainer($container);
126 $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\Entity', [$this->values, $this->entityTypeId]);
132 public function testId() {
133 $this->assertSame($this->values['id'], $this->entity->id());
139 public function testUuid() {
140 $this->assertSame($this->values['uuid'], $this->entity->uuid());
145 * @covers ::enforceIsNew
147 public function testIsNew() {
148 // We provided an ID, so the entity is not new.
149 $this->assertFalse($this->entity->isNew());
150 // Force it to be new.
151 $this->assertSame($this->entity, $this->entity->enforceIsNew());
152 $this->assertTrue($this->entity->isNew());
156 * @covers ::getEntityType
158 public function testGetEntityType() {
159 $this->assertSame($this->entityType, $this->entity->getEntityType());
165 public function testBundle() {
166 $this->assertSame($this->entityTypeId, $this->entity->bundle());
172 public function testLabel() {
173 // Make a mock with one method that we use as the entity's uri_callback. We
174 // check that it is called, and that the entity's label is the callback's
176 $callback_label = $this->randomMachineName();
177 $property_label = $this->randomMachineName();
178 $callback_container = $this->getMock(get_class());
179 $callback_container->expects($this->once())
180 ->method(__FUNCTION__)
181 ->will($this->returnValue($callback_label));
182 $this->entityType->expects($this->at(0))
183 ->method('getLabelCallback')
184 ->will($this->returnValue([$callback_container, __FUNCTION__]));
185 $this->entityType->expects($this->at(1))
186 ->method('getLabelCallback')
187 ->will($this->returnValue(NULL));
188 $this->entityType->expects($this->at(2))
191 ->will($this->returnValue('label'));
193 // Set a dummy property on the entity under test to test that the label can
194 // be returned form a property if there is no callback.
195 $this->entityTypeManager->expects($this->at(1))
196 ->method('getDefinition')
197 ->with($this->entityTypeId)
198 ->will($this->returnValue([
203 $this->entity->label = $property_label;
205 $this->assertSame($callback_label, $this->entity->label());
206 $this->assertSame($property_label, $this->entity->label());
212 public function testAccess() {
213 $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
214 $operation = $this->randomMachineName();
215 $access->expects($this->at(0))
217 ->with($this->entity, $operation)
218 ->will($this->returnValue(AccessResult::allowed()));
219 $access->expects($this->at(1))
220 ->method('createAccess')
221 ->will($this->returnValue(AccessResult::allowed()));
222 $this->entityTypeManager->expects($this->exactly(2))
223 ->method('getAccessControlHandler')
224 ->will($this->returnValue($access));
226 $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation));
227 $this->assertEquals(AccessResult::allowed(), $this->entity->access('create'));
233 public function testLanguage() {
234 $this->entityType->expects($this->any())
236 ->will($this->returnValueMap([
237 ['langcode', 'langcode'],
239 $this->assertSame('en', $this->entity->language()->getId());
243 * Setup for the tests of the ::load() method.
245 public function setupTestLoad() {
246 // Base our mocked entity on a real entity class so we can test if calling
247 // Entity::load() on the base class will bubble up to an actual entity.
248 $this->entityTypeId = 'entity_test_mul';
249 $methods = get_class_methods(EntityTestMul::class);
250 unset($methods[array_search('load', $methods)]);
251 unset($methods[array_search('loadMultiple', $methods)]);
252 unset($methods[array_search('create', $methods)]);
253 $this->entity = $this->getMockBuilder(EntityTestMul::class)
254 ->disableOriginalConstructor()
255 ->setMethods($methods)
263 * Tests Entity::load() when called statically on a subclass of Entity.
265 public function testLoad() {
266 $this->setupTestLoad();
268 $class_name = get_class($this->entity);
270 $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
271 $entity_type_repository->expects($this->once())
272 ->method('getEntityTypeFromClass')
274 ->willReturn($this->entityTypeId);
276 $storage = $this->getMock(EntityStorageInterface::class);
277 $storage->expects($this->once())
280 ->will($this->returnValue($this->entity));
282 $this->entityTypeManager->expects($this->once())
283 ->method('getStorage')
284 ->with($this->entityTypeId)
285 ->will($this->returnValue($storage));
287 \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
289 // Call Entity::load statically and check that it returns the mock entity.
290 $this->assertSame($this->entity, $class_name::load(1));
294 * @covers ::loadMultiple
296 * Tests Entity::loadMultiple() when called statically on a subclass of
299 public function testLoadMultiple() {
300 $this->setupTestLoad();
302 $class_name = get_class($this->entity);
304 $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
305 $entity_type_repository->expects($this->once())
306 ->method('getEntityTypeFromClass')
308 ->willReturn($this->entityTypeId);
310 $storage = $this->getMock(EntityStorageInterface::class);
311 $storage->expects($this->once())
312 ->method('loadMultiple')
314 ->will($this->returnValue([1 => $this->entity]));
316 $this->entityTypeManager->expects($this->once())
317 ->method('getStorage')
318 ->with($this->entityTypeId)
319 ->will($this->returnValue($storage));
321 \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
323 // Call Entity::loadMultiple statically and check that it returns the mock
325 $this->assertSame([1 => $this->entity], $class_name::loadMultiple([1]));
331 public function testCreate() {
332 $this->setupTestLoad();
334 $class_name = get_class($this->entity);
336 $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
337 $entity_type_repository->expects($this->once())
338 ->method('getEntityTypeFromClass')
340 ->willReturn($this->entityTypeId);
342 $storage = $this->getMock(EntityStorageInterface::class);
343 $storage->expects($this->once())
346 ->will($this->returnValue($this->entity));
348 $this->entityTypeManager->expects($this->once())
349 ->method('getStorage')
350 ->with($this->entityTypeId)
351 ->will($this->returnValue($storage));
353 \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
355 // Call Entity::create() statically and check that it returns the mock
357 $this->assertSame($this->entity, $class_name::create([]));
363 public function testSave() {
364 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
365 $storage->expects($this->once())
367 ->with($this->entity);
369 $this->entityTypeManager->expects($this->once())
370 ->method('getStorage')
371 ->with($this->entityTypeId)
372 ->will($this->returnValue($storage));
374 $this->entity->save();
380 public function testDelete() {
381 $this->entity->id = $this->randomMachineName();
382 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
383 // Testing the argument of the delete() method consumes too much memory.
384 $storage->expects($this->once())
387 $this->entityTypeManager->expects($this->once())
388 ->method('getStorage')
389 ->with($this->entityTypeId)
390 ->will($this->returnValue($storage));
392 $this->entity->delete();
396 * @covers ::getEntityTypeId
398 public function testGetEntityTypeId() {
399 $this->assertSame($this->entityTypeId, $this->entity->getEntityTypeId());
405 public function testPreSave() {
406 // This method is internal, so check for errors on calling it only.
407 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
408 // Our mocked entity->preSave() returns NULL, so assert that.
409 $this->assertNull($this->entity->preSave($storage));
415 public function testPostSave() {
416 $this->cacheTagsInvalidator->expects($this->at(0))
417 ->method('invalidateTags')
420 $this->entityTypeId . '_list',
422 $this->cacheTagsInvalidator->expects($this->at(1))
423 ->method('invalidateTags')
426 $this->entityTypeId . ':' . $this->values['id'],
428 $this->entityTypeId . '_list',
431 // This method is internal, so check for errors on calling it only.
432 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
434 // A creation should trigger the invalidation of the "list" cache tag.
435 $this->entity->postSave($storage, FALSE);
436 // An update should trigger the invalidation of both the "list" and the
438 $this->entity->postSave($storage, TRUE);
442 * @covers ::preCreate
444 public function testPreCreate() {
445 // This method is internal, so check for errors on calling it only.
446 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
448 // Our mocked entity->preCreate() returns NULL, so assert that.
449 $this->assertNull($this->entity->preCreate($storage, $values));
453 * @covers ::postCreate
455 public function testPostCreate() {
456 // This method is internal, so check for errors on calling it only.
457 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
458 // Our mocked entity->postCreate() returns NULL, so assert that.
459 $this->assertNull($this->entity->postCreate($storage));
463 * @covers ::preDelete
465 public function testPreDelete() {
466 // This method is internal, so check for errors on calling it only.
467 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
468 // Our mocked entity->preDelete() returns NULL, so assert that.
469 $this->assertNull($this->entity->preDelete($storage, [$this->entity]));
473 * @covers ::postDelete
475 public function testPostDelete() {
476 $this->cacheTagsInvalidator->expects($this->once())
477 ->method('invalidateTags')
479 $this->entityTypeId . ':' . $this->values['id'],
480 $this->entityTypeId . '_list',
482 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
483 $storage->expects($this->once())
484 ->method('getEntityType')
485 ->willReturn($this->entityType);
487 $entities = [$this->values['id'] => $this->entity];
488 $this->entity->postDelete($storage, $entities);
494 public function testPostLoad() {
495 // This method is internal, so check for errors on calling it only.
496 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
497 $entities = [$this->entity];
498 // Our mocked entity->postLoad() returns NULL, so assert that.
499 $this->assertNull($this->entity->postLoad($storage, $entities));
503 * @covers ::referencedEntities
505 public function testReferencedEntities() {
506 $this->assertSame([], $this->entity->referencedEntities());
510 * @covers ::getCacheTags
511 * @covers ::getCacheTagsToInvalidate
512 * @covers ::addCacheTags
514 public function testCacheTags() {
515 // Ensure that both methods return the same by default.
516 $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTags());
517 $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTagsToInvalidate());
519 // Add an additional cache tag and make sure only getCacheTags() returns
521 $this->entity->addCacheTags(['additional_cache_tag']);
523 // EntityTypeId is random so it can shift order. We need to duplicate the
524 // sort from \Drupal\Core\Cache\Cache::mergeTags().
525 $tags = ['additional_cache_tag', $this->entityTypeId . ':' . 1];
527 $this->assertEquals($tags, $this->entity->getCacheTags());
528 $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTagsToInvalidate());
532 * @covers ::getCacheContexts
533 * @covers ::addCacheContexts
535 public function testCacheContexts() {
536 $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
537 ->disableOriginalConstructor()
539 $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
541 $container = new ContainerBuilder();
542 $container->set('cache_contexts_manager', $cache_contexts_manager);
543 \Drupal::setContainer($container);
545 // There are no cache contexts by default.
546 $this->assertEquals([], $this->entity->getCacheContexts());
548 // Add an additional cache context.
549 $this->entity->addCacheContexts(['user']);
550 $this->assertEquals(['user'], $this->entity->getCacheContexts());
554 * @covers ::getCacheMaxAge
555 * @covers ::mergeCacheMaxAge
557 public function testCacheMaxAge() {
558 // Cache max age is permanent by default.
559 $this->assertEquals(Cache::PERMANENT, $this->entity->getCacheMaxAge());
561 // Set two cache max ages, the lower value is the one that needs to be
563 $this->entity->mergeCacheMaxAge(600);
564 $this->entity->mergeCacheMaxAge(1800);
565 $this->assertEquals(600, $this->entity->getCacheMaxAge());