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

197 lines
5.8 KiB
Markdown
Raw Normal View History

2024-02-07 22:25:53 +00:00
---
title: Lesson 3 - Building a Blog
permalink: /atdc/3-building-blog
---
{% block head_meta %}
<meta name="robots" content="noindex">
{% endblock %}
{% block content %}
In the previous two lessons, you've created a Drupal project with PHPUnit and have a running test suite that uses users and content created as part of each test.
With what you've learned, let's create a simple Blog module with tests and test-driven development.
## Creating the Blog page
First, let's create a page that will list the posts. This will be similar to our first tests for Drupal's front and admin pages.
Create a new `BlogPageTest` and have it extend `BrowserTestBase`.
Let's assert that a page should exist at `/blog` by returning a `200` status code, as this should be accessible by anonymous users.
```php
<?php
namespace Drupal\Tests\atdc\Functional;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\HttpFoundation\Response;
class BlogPageTest extends BrowserTestBase {
protected $defaultTheme = 'stark';
public function testBlogPage(): void {
$this->drupalGet('/blog');
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
}
}
```
As you haven't created it, the status code should be a `404` - causing the test to fail.
> Tip: you can use `--filter testBlogPage` to run a single test or `--stop-on-failure` to stop running the tests as soon as an error occurs. These should shorten the time to run your tests, as you only run the tests you need.
Whilst you could create the page using the Views module, let's create a custom route.
Create an `atdc.routing.yml` file:
```yaml
# web/modules/custom/atdc/atdc.routing.yml
atdc.blog:
path: /blog
defaults:
_controller: Drupal\atdc\Controller\BlogPageController
_title: Blog
requirements:
_permission: access content
```
With this added, the status code doesn't change and is a `404`.
Like in the previous lesson, you need to enable the `atdc` module by setting `$modules` in your test:
```php
protected static $modules = ['atdc'];
```
You'll also need to create an `atdc.info.yml` file so the module can be installed:
```yaml
# web/modules/custom/atdc/atdc.info.yml
name: ATDC
type: module
core_version_requirement: ^10
```
This should change the status code to a `403`, as you also need the `node` module for the `access content` permission:
```php
protected static $modules = ['node', 'atdc'];
```
This should cause the status code to change again - this time to a `500`.
This is progress.
The `atdc` module is being installed and enabled, and its routing file is being loaded. But, it references a Controller class that doesn't exist yet.
Let's do that next.
## Creating a BlogPageController
Create the expected Controller class within a `src/Controller` directory:
```php
<?php
// web/modules/custom/atdc/src/Controller/BlogPageController.php
namespace Drupal\atdc\Controller;
class BlogPageController {
}
```
Note the namespace is different from the one within the test classes and we don't need to extend any other classes.
For this step, the simplest thing you can do to get a passing test is to return an empty render array.
As long as it's an array, even an empty one, the test should pass:
```php
public function __invoke(): array {
return [];
}
```
As a rule, you want the tests to pass as often and quickly as possible by doing the simplest thing to achieve it - even returning a hard-coded value or an empty array.
Now the test passes, you can add to it and drive out the next piece of functionality.
This is also a good time to do a `git commit`.
## Asserting posts are visible
Again, let's start with a new test:
```php
public function testPostsAreVisible(): void {
// Arrange.
$this->createNode(['type' => 'post', 'title' => 'First post']);
$this->createNode(['type' => 'post', 'title' => 'Second post']);
$this->createNode(['type' => 'post', 'title' => 'Third post']);
// Act.
$this->drupalGet('/blog');
// Assert.
$assert = $this->assertSession();
$assert->pageTextContains('First post');
$assert->pageTextContains('Second post');
$assert->pageTextContains('Third post');
}
```
As we're returning an empty array within `BlogPageController`, the page will have no content and this test will fail with a message like:
> 1) Drupal\Tests\atdc\Functional\BlogPageTest::testPostsAreVisible
> Behat\Mink\Exception\ResponseTextException: The text "First post" was not found anywhere in the text of the current page.
Start by extending the `ControllerBase` base class within your Controller:
```diff
+ use Drupal\Core\Controller\ControllerBase;
+
- class BlogPageController {
+ class BlogPageController extends ControllerBase {
```
Now, within the `__invoke` method, add this to return a list of each node title:
```php
public function __invoke(): array {
$nodeStorage = $this->entityTypeManager()->getStorage('node');
$nodes = $nodeStorage->loadMultiple();
$build = [];
$build['content']['#theme'] = 'item_list';
foreach ($nodes as $node) {
$build['content']['#items'][] = $node->label();
}
return $build;
}
```
As the node titles are within the page content, the test should pass.
To be confident, try returning an empty array again or removing the foreach loop, seeing the test fail, and reverting the change.
Confidence comes from tests that pass and fail when expected, so you're sure the correct behaviour is being tested, and the tests aren't passing accidentally.
You can add further tests, such as checking that only nodes of a specified node type are returned. Currently, all nodes would be listed, even if they aren't posts.
## Asserting posts are in the correct order
We have a list of post titles on a page and a test to prove it, but what if we want to ensure the posts are shown in a specified order?
That's harder to do with a functional test, so in the next lesson, we'll refactor the code and look at Kernel tests.
{% endblock %}