--- title: Lesson 6 - Builders and custom assertions permalink: /atdc/6-builders-custom-assertions --- {% block head_meta %} {% 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: ```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`: ```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`: ```php 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. ```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`: ```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: ```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: ```php /** * @param array $expectedTitles * @param array $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: ```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 %}