7

I'm working on a project where we need to have code coverage using phpunit. I have a public method that includes this code:

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];

    foreach ($items as $delta => $item) {
      $elements[$delta] = $this->viewValue($item);
    }

    return $elements;
  }

and dependent private method:

  /**
   * Generate the output appropriate for one field item.
   *
   * @param \Drupal\Core\Field\FieldItemInterface $item
   *   One field item.
   *
   * @return string
   *   The textual output generated.
   */
  protected function viewValue(FieldItemInterface $item) {
    $frequency = $item->getValue();
    return [
      '#theme' => 'frequency',
      '#frequency' => $this->switchWeights($frequency['value']),
      '#limit' => 5,
    ];
  }

I am trying to cover with unit test the parent method to make sure that it works as suspected. Here my code with no items provided to field to be formatted just covered with abstract class mock:

/**
 * Simple test to cover FrequencyFieldFormatter class.
 *
 * @group my_custom_module_name
 */
class FrequencyFieldFormatterTest extends UnitTestCase {

  /**
   * Test viewElements function.
   */
  public function testviewElements() {

    /** @var \Drupal\Core\Field\FieldItemList|\PHPUnit\Framework\MockObject\MockObject $field_list */
    $field_list = $this
      ->getMockBuilder(FieldItemListInterface::class)
      ->getMockForAbstractClass();

    $resultElements = FrequencyFieldFormatter::viewElements($field_list, 'und');
    $this->assertEmpty($resultElements);
  }

}

When i trying to change assertion to make sure that items field_items processed to be rendered with custom theme but i am facing difficulty when instead providing a mock for abstract class which is specified as interface i providing a mock for real class \Drupal\Core\Field\FieldItemList:

$field_list = $this
      ->getMockBuilder(\Drupal\Core\Field\FieldItemList::class)
      ->getMock();

I know that the error i am getting in this case is related to not initialized container for this stack:

There was 1 error:

1) Drupal\Test\my_custom_module_name\Unit\FrequencyFieldFormatterTest::testviewElements
ArgumentCountError: Too few arguments to function Drupal\Core\TypedData\TypedData::__construct(), 0 passed in /Users/username/Sites/drupal-site-09/code/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php on line 607 and at least 1 expected

Then i tried to initialize container to make it works but i do not know how to do it properly.

May somebody help me?

1 Answer 1

2

FieldFormatters do not profit from unit tests as they are too much abstracted away from the typical usage scenario, so I would advise against creating them for formatters.

In case you have a really large implementation, you might consider extracting those aspects into methods or classes and have these unit tested based on a more restrictive API than just the FieldItemListInterface.

And as you have noticed, you need to mock away too many services and the parts of the eco system.

To ensure your formatter will actually work, these questions should be answered by a test:

  • Is the formatter plugin picked up by the system?
  • Does the renderer produce what you expect you are producing?
  • Is the caching layer going to get the correct cacheability meta data?
  • Are variants of the data you expect via $field_items covered: empty field, single field, multiple fields?

Pretty much all formatters in Core and the usual modules opt for an integration test based on KernelTestBase.

.../modules/custom/src/Plugin/Field/FieldFormatter/CustomFormatter.php

<?php
namespace Drupal\custom\Plugin\Field\FieldFormatter;

/**
 * @FieldFormatter(
 *   id = "custom_datetime",
 *   label = @Translation("Custom Date & Time"),
 *   field_types = {
 *     "datetime"
 *   }
 * )
 */
class CustomFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    return [
      '#markup' => $this->t('Completed'),
    ];
  }

}

.../modules/custom/tests/src/Kernel/CustomFormatterTest.php

<?php

namespace Drupal\Tests\custom\Kernel;

use Drupal\Component\Render\MarkupInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Tests the date time formatters functionality.
 *
 * @group custom
 */
class CustomFormatterTest extends EntityKernelTestBase {

  /**
   * The entity type used in this test.
   *
   * @var string
   */
  protected $entityType = 'entity_test';

  /**
   * The bundle used in this test.
   *
   * @var string
   */
  protected $bundle = 'entity_test';

  /**
   * Modules to enable.
   *
   * @var array
   */
  public static $modules = ['datetime', 'language', 'custom'];

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();

    FieldStorageConfig::create([
      'field_name' => 'field_under_test',
      'entity_type' => $this->entityType,
      'type' => 'datetime',
      'settings' => [],
    ])->save();
    FieldConfig::create([
      'entity_type' => $this->entityType,
      'bundle' => $this->bundle,
      'field_name' => 'field_under_test',
      'label' => 'Field label',
    ])->save();
  }

  protected function createAndRenderFieldData(array $field_data): MarkupInterface {
    // Create the entity to be referenced.
    $entity = $this->container->get('entity_type.manager')
      ->getStorage($this->entityType)
      ->create(['name' => $this->randomMachineName()]);
    $entity->field_under_test = $field_data;
    $entity->save();
    $build = $entity->get('field_under_test')->view(['type' => 'custom_datetime']);
    return \Drupal::service('renderer')->renderRoot($build);
  }

  /**
   * Tests the formatter's output with a date in the past.
   */
  public function testFormatterWithDateInThePast() {
    // Verify the text field formatter's render array.
    $result = $this->createAndRenderFieldData(['value' => '2021-01-01T12:00:00']);
    $this->assertEqual($result, "Completed");
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.