7.8 KiB
title | permalink |
---|---|
ATDC: Lesson 5 - Testing Post Ordering | /atdc/5-testing-post-ordering |
{% block head_meta %}
{% 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
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
:
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:
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:
// 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:
// 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.
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:
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.
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:
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:
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 %}