Add ATDC course pages
This commit is contained in:
parent
11ae0f9fc7
commit
72c3d50cf7
11 changed files with 2178 additions and 0 deletions
192
source/_pages/atdc/6.md
Normal file
192
source/_pages/atdc/6.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
---
|
||||
title: 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:
|
||||
|
||||
```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
|
||||
<?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:
|
||||
|
||||
```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.
|
||||
|
||||
```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<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:
|
||||
|
||||
```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 %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue