---
title: 'ATDC: Lesson 6 - Builders and custom assertions'
permalink: /atdc/6-builders-custom-assertions
---

{% block head_meta %}
<meta name="robots" content="noindex">
{% endblock %}

{% block content %}
In yesterday's lesson, you created your first Kernel test and used it to ensure the posts are returned from `PostNodeRepository` in the desired order.

This is how we're creating the posts currently:

```language-php
$this->createNode([
  'created' => (new DrupalDateTime('-1 week'))->getTimestamp(),
  'title' => 'Post one',
  'type' => 'post',
]);

$this->createNode([
  'created' => (new DrupalDateTime('-8 days'))->getTimestamp(),
  'title' => 'Post two',
  'type' => 'post',
]);

$this->createNode([
  'created' => (new DrupalDateTime('yesterday'))->getTimestamp(),
  'title' => 'Post three',
  'type' => 'post',
]);
```

The Builder pattern is another design pattern I like, which makes it easier to build complex objects.

Let's create a Builder class to create the posts.

## Creating a PostBuilder class

This is how I'd like to create a post using a `PostBuilder`:

```language-php
PostBuilder::create()
  ->setCreatedDate('-1 week')
  ->setTitle('Post one')
  ->getPost();
```

This makes it easier to do by creating named methods for each value we want to set and not relying on array keys whilst also moving implementation details like using `DrupalDateTime` to set the `created` date.

To do this, create a new class at `src/Builder/PostBuilder.php`:

```language-php
<?php

// web/modules/custom/atdc/src/Builder/PostBuilder.php

namespace Drupal\atdc\Builder;

final class PostBuilder {

  public static function create(): self {
    return new self();
  }

}
```

It should be within the `Drupal\atdc\Builder` namespace and has a static `create` method that works as a named constructor and makes `PostBuilder::create()` work.

As it returns a new version of `self`, you can also chain methods onto it.

Add the additional methods and properties:

```language-php
private ?DrupalDateTime $created = NULL;

private string $title;

public function setCreatedDate(string $time = 'now'): self {
  $this->created = new DrupalDateTime($time);

  return $this;
}

public function setTitle(string $title): self {
  $this->title = $title;

  return $this;
}
```

Again, by returning `$this`, we can keep chaining methods.

Finally, create the `getPost()` method that creates the node based on the property values, saves it, and returns it.

```language-php
public function getPost(): NodeInterface {
  $post = Node::create([
    'created' => $this->created?->getTimestamp(),
    'title' => $this->title,
    'type' => 'post',
  ]);

  $post->save();

  return $post;
}
```

Now, refactor the test to use the `PostBuilder`:

```language-php
PostBuilder::create()
  ->setCreatedDate('-1 week')
  ->setTitle('Post one')
  ->getPost();

PostBuilder::create()
  ->setCreatedDate('-8 days')
  ->setTitle('Post two')
  ->getPost();

PostBuilder::create()
  ->setCreatedDate('yesterday')
  ->setTitle('Post three')
  ->getPost();
```

Doing this simplifies the test and makes it easier to extend in the future by adding more methods to `PostBuilder`.

## Creating a custom assertion

Finally, for today, let's refactor the assertion that verifies the titles are returned in the correct order.

This is the current assertion:

```language-php
self::assertSame(
  ['Post two', 'Post one', 'Post three'],
  array_map(
    fn (NodeInterface $node) => $node->label(),
    $nodes
  )
);
```

We create an array of expected titles and compare that to an array created from `array_map`.

We can make this more reusable and readable by extracting this into a new custom assertion, which is just another static method.

Create a new static function at the bottom of the class with a name that describes what it's asserting:

```language-php
/**
 * @param array<int, string> $expectedTitles
 * @param array<int, NodeInterface> $nodes
 */
private static function assertNodeTitlesAreSame(
  array $expectedTitles,
  array $nodes,
): void {
  self::assertSame(
    $expectedTitles,
    array_map(
      fn (NodeInterface $node) => $node->label(),
      $nodes
    )
  );
}
```

We can add arguments for the arrays of titles and nodes, and be explicit about what they contain by adding a docblock.

In this method, we can do the same logic and use `array_map` to create a list of node titles and compare them to the expected titles.

The benefits are that this now has a name that describes what we're asserting, and because it's a separate method, it can be reused in the same test or moved to a base class and used elsewhere.

Finally, refactor the test to use the new assertion:

```language-php
self::assertNodeTitlesAreSame(
  ['Post two', 'Post one', 'Post three'],
  $nodes,
);
```

In my opinion, this is a lot better.

In tomorrow's lesson, let's add some more tests to the `PostNodeRepository` that we skipped in previous lessons.
{% endblock %}