diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityViewBuilderTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityViewBuilderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..29d3d17dba0fb9456718173fc12621eb32000f8e
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityViewBuilderTest.php
@@ -0,0 +1,80 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityViewBuilder;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\EntityViewBuilder
+ * @group Entity
+ */
+class EntityViewBuilderTest extends UnitTestCase {
+
+  const string ENTITY_TYPE_ID = 'test_entity_type';
+
+  /**
+   * The entity view builder under test.
+   *
+   * @var \Drupal\Core\Entity\EntityViewBuilder
+   */
+  protected EntityViewBuilder $viewBuilder;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->viewBuilder = new class() extends EntityViewBuilder {
+
+      public function __construct() {
+        $this->entityTypeId = EntityViewBuilderTest::ENTITY_TYPE_ID;
+      }
+
+    };
+  }
+
+  /**
+   * Tests build components using a mocked Iterator.
+   */
+  public function testBuildComponents(): void {
+    $field_name = $this->randomMachineName();
+    $bundle = $this->randomMachineName();
+    $entity_id = mt_rand(20, 30);
+    $field_item_list = $this->createStub(FieldItemListInterface::class);
+    $item = new \stdClass();
+    $this->setupMockIterator($field_item_list, [$item]);
+    $entity = $this->createConfiguredStub(FieldableEntityInterface::class, [
+      'bundle' => $bundle,
+      'hasField' => TRUE,
+      'get' => $field_item_list,
+    ]);
+    $formatter_result = [
+      $entity_id => ['#' . $this->randomMachineName() => $this->randomString()],
+    ];
+    $display = $this->createConfiguredStub(EntityViewDisplayInterface::class, [
+      'getComponents' => [$field_name => []],
+      'buildMultiple' => $formatter_result,
+    ]);
+    $entities = [$entity_id => $entity];
+    $displays = [$bundle => $display];
+    $build = [$entity_id => []];
+    $view_mode = $this->randomMachineName();
+    // Assert the hook is invoked.
+    $module_handler = $this->createMock(ModuleHandlerInterface::class);
+    $module_handler->expects($this->once())
+      ->method('invokeAll')
+      ->with('entity_prepare_view', [self::ENTITY_TYPE_ID, $entities, $displays, $view_mode]);
+    $this->viewBuilder->setModuleHandler($module_handler);
+    $this->viewBuilder->buildComponents($build, $entities, $displays, $view_mode);
+    $this->assertSame([], $item->_attributes);
+    $this->assertSame($formatter_result, $build);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php
index 2257148d0698c6bf88b9b75e5f601ad2ce790f76..b279bd1ed250395f037a36d001f6e2d05a2ed49b 100644
--- a/core/tests/Drupal/Tests/UnitTestCase.php
+++ b/core/tests/Drupal/Tests/UnitTestCase.php
@@ -13,6 +13,7 @@
 use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait;
 use Drupal\TestTools\Extension\Dump\DebugDump;
 use PHPUnit\Framework\Attributes\BeforeClass;
+use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 use Prophecy\PhpUnit\ProphecyTrait;
 use Symfony\Component\VarDumper\VarDumper;
@@ -210,4 +211,32 @@ protected function getClassResolverStub() {
     return $class_resolver;
   }
 
+  /**
+   * Set up a traversable class mock to return specific items when iterated.
+   *
+   * Test doubles for types extending \Traversable are required to implement
+   * \Iterator which requires setting up five methods. Instead, this helper
+   * can be used.
+   *
+   * @param \PHPUnit\Framework\MockObject\MockObject&\Iterator $mock
+   *   A mock object mocking a traversable class.
+   * @param array $items
+   *   The items to return when this mock is iterated.
+   *
+   * @return \PHPUnit\Framework\MockObject\MockObject&\Iterator
+   *   The same mock object ready to be iterated.
+   *
+   * @template T of \PHPUnit\Framework\MockObject\MockObject&\Iterator
+   * @phpstan-param T $mock
+   * @phpstan-return T
+   * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103
+   */
+  protected function setupMockIterator(MockObject&\Iterator $mock, array $items): MockObject&\Iterator {
+    $iterator = new \ArrayIterator($items);
+    foreach (get_class_methods(\Iterator::class) as $method) {
+      $mock->method($method)->willReturnCallback([$iterator, $method]);
+    }
+    return $mock;
+  }
+
 }