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());
173 public function testLabel() {
174 // Make a mock with one method that we use as the entity's uri_callback. We
175 // check that it is called, and that the entity's label is the callback's
177 $callback_label = $this->randomMachineName();
178 $property_label = $this->randomMachineName();
179 $callback_container = $this->getMock(get_class());
180 $callback_container->expects($this->once())
181 ->method(__FUNCTION__)
182 ->will($this->returnValue($callback_label));
183 $this->entityType->expects($this->at(0))
184 ->method('getLabelCallback')
185 ->will($this->returnValue([$callback_container, __FUNCTION__]));
186 $this->entityType->expects($this->at(1))
187 ->method('getLabelCallback')
188 ->will($this->returnValue(NULL));
189 $this->entityType->expects($this->at(2))
192 ->will($this->returnValue('label'));
194 // Set a dummy property on the entity under test to test that the label can
195 // be returned form a property if there is no callback.
196 $this->entityTypeManager->expects($this->at(1))
197 ->method('getDefinition')
198 ->with($this->entityTypeId)
199 ->will($this->returnValue([
204 $this->entity->label = $property_label;
206 $this->assertSame($callback_label, $this->entity->label());
207 $this->assertSame($property_label, $this->entity->label());
213 public function testAccess() {
214 $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
215 $operation = $this->randomMachineName();
216 $access->expects($this->at(0))
218 ->with($this->entity, $operation)
219 ->will($this->returnValue(AccessResult::allowed()));
220 $access->expects($this->at(1))
221 ->method('createAccess')
222 ->will($this->returnValue(AccessResult::allowed()));
223 $this->entityTypeManager->expects($this->exactly(2))
224 ->method('getAccessControlHandler')
225 ->will($this->returnValue($access));
227 $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation));
228 $this->assertEquals(AccessResult::allowed(), $this->entity->access('create'));
234 public function testLanguage() {
235 $this->entityType->expects($this->any())
237 ->will($this->returnValueMap([
238 ['langcode', 'langcode'],
240 $this->assertSame('en', $this->entity->language()->getId());
244 * Setup for the tests of the ::load() method.
246 public function setupTestLoad() {
247 // Base our mocked entity on a real entity class so we can test if calling
248 // Entity::load() on the base class will bubble up to an actual entity.
249 $this->entityTypeId = 'entity_test_mul';
250 $methods = get_class_methods(EntityTestMul::class);
251 unset($methods[array_search('load', $methods)]);
252 unset($methods[array_search('loadMultiple', $methods)]);
253 unset($methods[array_search('create', $methods)]);
254 $this->entity = $this->getMockBuilder(EntityTestMul::class)
255 ->disableOriginalConstructor()
256 ->setMethods($methods)
264 * Tests Entity::load() when called statically on a subclass of Entity.
266 public function testLoad() {
267 $this->setupTestLoad();
269 $class_name = get_class($this->entity);
271 $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
272 $entity_type_repository->expects($this->once())
273 ->method('getEntityTypeFromClass')
275 ->willReturn($this->entityTypeId);
277 $storage = $this->getMock(EntityStorageInterface::class);
278 $storage->expects($this->once())
281 ->will($this->returnValue($this->entity));
283 $this->entityTypeManager->expects($this->once())
284 ->method('getStorage')
285 ->with($this->entityTypeId)
286 ->will($this->returnValue($storage));
288 \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
290 // Call Entity::load statically and check that it returns the mock entity.
291 $this->assertSame($this->entity, $class_name::load(1));
295 * @covers ::loadMultiple
297 * Tests Entity::loadMultiple() when called statically on a subclass of
300 public function testLoadMultiple() {
301 $this->setupTestLoad();
303 $class_name = get_class($this->entity);
305 $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
306 $entity_type_repository->expects($this->once())
307 ->method('getEntityTypeFromClass')
309 ->willReturn($this->entityTypeId);
311 $storage = $this->getMock(EntityStorageInterface::class);
312 $storage->expects($this->once())
313 ->method('loadMultiple')
315 ->will($this->returnValue([1 => $this->entity]));
317 $this->entityTypeManager->expects($this->once())
318 ->method('getStorage')
319 ->with($this->entityTypeId)
320 ->will($this->returnValue($storage));
322 \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
324 // Call Entity::loadMultiple statically and check that it returns the mock
326 $this->assertSame([1 => $this->entity], $class_name::loadMultiple([1]));
332 public function testCreate() {
333 $this->setupTestLoad();
335 $class_name = get_class($this->entity);
337 $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
338 $entity_type_repository->expects($this->once())
339 ->method('getEntityTypeFromClass')
341 ->willReturn($this->entityTypeId);
343 $storage = $this->getMock(EntityStorageInterface::class);
344 $storage->expects($this->once())
347 ->will($this->returnValue($this->entity));
349 $this->entityTypeManager->expects($this->once())
350 ->method('getStorage')
351 ->with($this->entityTypeId)
352 ->will($this->returnValue($storage));
354 \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
356 // Call Entity::create() statically and check that it returns the mock
358 $this->assertSame($this->entity, $class_name::create([]));
364 public function testSave() {
365 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
366 $storage->expects($this->once())
368 ->with($this->entity);
370 $this->entityTypeManager->expects($this->once())
371 ->method('getStorage')
372 ->with($this->entityTypeId)
373 ->will($this->returnValue($storage));
375 $this->entity->save();
381 public function testDelete() {
382 $this->entity->id = $this->randomMachineName();
383 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
384 // Testing the argument of the delete() method consumes too much memory.
385 $storage->expects($this->once())
388 $this->entityTypeManager->expects($this->once())
389 ->method('getStorage')
390 ->with($this->entityTypeId)
391 ->will($this->returnValue($storage));
393 $this->entity->delete();
397 * @covers ::getEntityTypeId
399 public function testGetEntityTypeId() {
400 $this->assertSame($this->entityTypeId, $this->entity->getEntityTypeId());
406 public function testPreSave() {
407 // This method is internal, so check for errors on calling it only.
408 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
409 // Our mocked entity->preSave() returns NULL, so assert that.
410 $this->assertNull($this->entity->preSave($storage));
416 public function testPostSave() {
417 $this->cacheTagsInvalidator->expects($this->at(0))
418 ->method('invalidateTags')
421 $this->entityTypeId . '_list',
423 $this->cacheTagsInvalidator->expects($this->at(1))
424 ->method('invalidateTags')
427 $this->entityTypeId . ':' . $this->values['id'],
429 $this->entityTypeId . '_list',
432 // This method is internal, so check for errors on calling it only.
433 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
435 // A creation should trigger the invalidation of the "list" cache tag.
436 $this->entity->postSave($storage, FALSE);
437 // An update should trigger the invalidation of both the "list" and the
439 $this->entity->postSave($storage, TRUE);
443 * @covers ::preCreate
445 public function testPreCreate() {
446 // This method is internal, so check for errors on calling it only.
447 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
449 // Our mocked entity->preCreate() returns NULL, so assert that.
450 $this->assertNull($this->entity->preCreate($storage, $values));
454 * @covers ::postCreate
456 public function testPostCreate() {
457 // This method is internal, so check for errors on calling it only.
458 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
459 // Our mocked entity->postCreate() returns NULL, so assert that.
460 $this->assertNull($this->entity->postCreate($storage));
464 * @covers ::preDelete
466 public function testPreDelete() {
467 // This method is internal, so check for errors on calling it only.
468 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
469 // Our mocked entity->preDelete() returns NULL, so assert that.
470 $this->assertNull($this->entity->preDelete($storage, [$this->entity]));
474 * @covers ::postDelete
476 public function testPostDelete() {
477 $this->cacheTagsInvalidator->expects($this->once())
478 ->method('invalidateTags')
480 $this->entityTypeId . ':' . $this->values['id'],
481 $this->entityTypeId . '_list',
483 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
484 $storage->expects($this->once())
485 ->method('getEntityType')
486 ->willReturn($this->entityType);
488 $entities = [$this->values['id'] => $this->entity];
489 $this->entity->postDelete($storage, $entities);
495 public function testPostLoad() {
496 // This method is internal, so check for errors on calling it only.
497 $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
498 $entities = [$this->entity];
499 // Our mocked entity->postLoad() returns NULL, so assert that.
500 $this->assertNull($this->entity->postLoad($storage, $entities));
504 * @covers ::referencedEntities
506 public function testReferencedEntities() {
507 $this->assertSame([], $this->entity->referencedEntities());
511 * @covers ::getCacheTags
512 * @covers ::getCacheTagsToInvalidate
513 * @covers ::addCacheTags
515 public function testCacheTags() {
516 // Ensure that both methods return the same by default.
517 $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTags());
518 $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTagsToInvalidate());
520 // Add an additional cache tag and make sure only getCacheTags() returns
522 $this->entity->addCacheTags(['additional_cache_tag']);
524 // EntityTypeId is random so it can shift order. We need to duplicate the
525 // sort from \Drupal\Core\Cache\Cache::mergeTags().
526 $tags = ['additional_cache_tag', $this->entityTypeId . ':' . 1];
528 $this->assertEquals($tags, $this->entity->getCacheTags());
529 $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTagsToInvalidate());
533 * @covers ::getCacheContexts
534 * @covers ::addCacheContexts
536 public function testCacheContexts() {
537 $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
538 ->disableOriginalConstructor()
540 $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
542 $container = new ContainerBuilder();
543 $container->set('cache_contexts_manager', $cache_contexts_manager);
544 \Drupal::setContainer($container);
546 // There are no cache contexts by default.
547 $this->assertEquals([], $this->entity->getCacheContexts());
549 // Add an additional cache context.
550 $this->entity->addCacheContexts(['user']);
551 $this->assertEquals(['user'], $this->entity->getCacheContexts());
555 * @covers ::getCacheMaxAge
556 * @covers ::mergeCacheMaxAge
558 public function testCacheMaxAge() {
559 // Cache max age is permanent by default.
560 $this->assertEquals(Cache::PERMANENT, $this->entity->getCacheMaxAge());
562 // Set two cache max ages, the lower value is the one that needs to be
564 $this->entity->mergeCacheMaxAge(600);
565 $this->entity->mergeCacheMaxAge(1800);
566 $this->assertEquals(600, $this->entity->getCacheMaxAge());