--- title: 'ATDC: Lesson 3 - Building a Blog' permalink: /atdc/3-building-blog --- {% block head_meta %} {% 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. ```language-php 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: ```language-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: ```language-php protected static $modules = ['atdc']; ``` You'll also need to create an `atdc.info.yml` file so the module can be installed: ```language-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: ```language-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: ```language-php 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: ```language-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 %}