197 lines
5.8 KiB
Markdown
197 lines
5.8 KiB
Markdown
---
|
|
title: 'ATDC: 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 %}
|