oliverdavies.uk/source/_pages/atdc/5.md

251 lines
7.8 KiB
Markdown
Raw Normal View History

2024-02-07 22:25:53 +00:00
---
2024-02-08 00:00:16 +00:00
title: 'ATDC: Lesson 5 - Testing Post Ordering'
2024-02-07 22:25:53 +00:00
permalink: /atdc/5-testing-post-ordering
---
{% block head_meta %}
<meta name="robots" content="noindex">
{% endblock %}
{% block content %}
Now we have the repository in place from the last lesson, let's move on to testing that the posts are returned in the correct order.
We want them to be returned based on their published date and not by their node ID or anything else.
To do this, we will use a different type of test - a Kernel test.
## Introducing Kernel tests
So far, we've been using Functional (or Browser) tests to ensure the blog page exists and that the correct posts are displayed.
It's easy to assert the correct posts are shown on the page, but it's much harder to assert they're shown in the right order.
This is much easier to do with a Kernel test (aka. an integration test).
Instead of making HTTP requests and checking the responses, we can test the results from the repository and ensure it returns the results in the correct order.
## Writing your first Kernel test
Let's create a new test that uses the `PostNodeRepository` to find the nodes and assert we get an expected number returned.
```php
<?php
namespace Drupal\Tests\atdc\Kernel;
use Drupal\atdc\Repository\PostNodeRepository;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
class PostNodeRepositoryTest extends EntityKernelTestBase {
public function testPostsAreReturnedByCreatedDate(): void {
// Arrange.
// Act.
$postRepository = $this->container->get(PostNodeRepository::class);
assert($postRepository instanceof PostNodeRepository);
$nodes = $postRepository->findAll();
// Assert.
self::assertCount(3, $nodes);
}
}
```
As with the Functional test, the file and class name must have a `Test` suffix, and test methods should have a `test` prefix. As we're testing the `PostNodeRepository` class, the convention is to name the test `PostNodeRepositoryTest`.
As this is a Kernel test, it should be placed within the `tests/src/Kernel` directory and extend the `EntityKernelTestBase` class.
We could extend others, such as the regular `KernelTestBase`, but as we'll be working with nodes, `EntityKernelTestBase` is the better option.
Instead of making assertions based on the HTTP response, we're testing what's returned from the `findAll()` method.
### Resolving setup test failures
Run the tests to see the first error:
> Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "Drupal\atdc\Repository\PostNodeRepository".
Although you created the `PostNodeRepository` in the previous lesson and added it as a service, it's not found.
It's defined within `atdc.services.yml`, but we need to be explicit about which modules are enabled when running the test, and the `atdc` module isn't enabled.
Create a `$modules` array within the test class and add `atdc`:
```php
protected static $modules = ['atdc'];
```
Run the tests again, and you should get a different error:
> Drupal\Component\Plugin\Exception\PluginNotFoundException: The "node" entity type does not exist.
As well as `atdc`, you must enable the `node` module. You can do so by adding it to the `$modules` array:
```php
protected static $modules = ['node', 'atdc'];
```
Now, you should get a logic error instead of a setup error:
> Failed asserting that actual size 0 matches expected size 3.
Typically, Kernel tests have more setup steps, such as installing module configuration and creating specific database tables.
But, although they can be more complicated to set up, they're faster to run compared to Functional tests.
The type of test you pick will depend on what you're trying to test.
## Creating posts
Currently, the 'Arrange' step within the test is empty, and whilst we're asserting there should be three posts returned, none are.
We need to create some posts within the test.
To access the `createNode()` method we've used in previous lessons within a Kernel test, you must use the `NodeCreationTrait`.
Add `use NodeCreationTrait` within the test class and `use Drupal\Tests\node\Traits\NodeCreationTrait;` as an import if needed.
Within the test, you can use `$this->createNode()` to create posts.
Create the three posts the test is expecting:
```php
// Arrange.
$this->createNode(['type' => 'post']);
$this->createNode(['type' => 'post']);
$this->createNode(['type' => 'post']);
```
This should be enough for the test to pass.
## Adding assertions for the order
Next, let's assert they're returned in a specific order.
Update the posts to have a specific title and created date so we can specify which order we expect them to be returned in and which titles they should have:
```php
// Arrange.
$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',
]);
```
Note we're intentionally setting them to be in an incorrect order, to begin with, so the test doesn't pass accidentally. This way, we can see it fail and know the task is complete once it passes.
Next, assert that the titles are returned in the correct order.
```php
self::assertSame(
['Post two', 'Post one', 'Post three'],
array_map(
fn (NodeInterface $node) => $node->label(),
$nodes
)
);
```
For each node in `$nodes`, get its label (title) and compare them with the titles in the order we want.
As expected, the test fails:
```plain
1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
- 0 => 'Post two'
1 => 'Post one'
- 2 => 'Post three'
+ 2 => 'Post two'
+ 3 => 'Post three'
)
```
We want `Post two` to be returned first, followed by `Post one` and `Post three`.
## Fixing the ordering
We need to update the code within `PostNodeRepository` to fix the ordering.
After loading the nodes, we need to sort them.
```php
public function findAll(): array {
$nodeStorage = $this->entityTypeManager->getStorage('node');
$nodes = $nodeStorage->loadMultiple();
uasort($nodes, function (NodeInterface $a, NodeInterface $b): int {
return $a->getCreatedTime() <=> $b->getCreatedTime();
});
return $nodes;
}
```
This sorts the nodes based on their created time in the desired order and returns them.
This gets us further, but the test is still failing.
Whilst the order is correct, the array keys don't match what we expect:
```plain
1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
Array &0 (
- 0 => 'Post two'
+ 2 => 'Post two'
1 => 'Post one'
- 2 => 'Post three'
+ 3 => 'Post three'
)
```
Finally, replace `return $nodes;` with `return array_values($nodes)` to reset the keys before returning them.
This should give us a passing test:
> OK (1 test, 3 assertions)
And, because the correct titles are still being shown, our original Functional tests still pass, too:
> OK (3 tests, 16 assertions)
Tip: to see the names of the tests in your output, add the `--testdox` flag to the `phpunit` command:
```plain
Blog Page (Drupal\Tests\atdc\Functional\BlogPage)
✔ Blog page
✔ Posts are visible
Post Node Repository (Drupal\Tests\atdc\Kernel\PostNodeRepository)
✔ Posts are returned by created date
```
## Conclusion
In today's lesson, you learned about Kernel tests and wrote one to test the ordering of the posts returned from the `PostNodeRepository`.
Tomorrow, we'll refactor `PostNodeRepositoryTest` to use a `Builder` class and a custom assertion method.
{% endblock %}