--- title: 'ATDC: Lesson 7 - Filling in old tests' permalink: /atdc/7-filling-in-old-tests --- {% block head_meta %} {% endblock %} {% block content %} In lesson 3, I mentioned that the current code has some gaps. We checked the expected nodes were shown but not the opposite - the nodes we didn't expect to see weren't shown. Let's fix that in this lesson. ## Only returning published nodes First, let's ensure that only published nodes are returned and displayed on the page. We can do this easily with a functional test, so add a new test method to `BlogPostTest`: ```language-php public function testOnlyPublishedNodesAreShown(): void { PostBuilder::create() ->setTitle('Post one') ->isPublished() ->getPost(); PostBuilder::create() ->setTitle('Post two') ->isNotPublished() ->getPost(); PostBuilder::create() ->setTitle('Post three') ->isPublished() ->getPost(); $this->drupalGet('/blog'); $assert = $this->assertSession(); $assert->pageTextContains('Post one'); $assert->pageTextNotContains('Post two'); $assert->pageTextContains('Post three'); } ``` Import the `PostBuilder` by adding `use Drupal\atdc\Builder\PostBuilder;` if needed, and run the test to see the first error: > Error: Call to undefined method Drupal\atdc\Builder\PostBuilder::isPublished() In this test, we want to create some published and unpublished posts and assert only the published ones are shown, but we don't have this functionality on the `PostBuilder`. To fix the error, add this function so it exists: ```language-php public function isPublished(): self { return $this; } ``` We'll revisit this later once we have a failing test that requires further changes. Running the tests again, you should get this unexpected error: > PDOException: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: node_field_data.created When using `PostBuilder` in the previous lesson, we were always providing a created date, but, as we're not doing that in this test, the created date is `NULL`, causing this error. Update the `getPost()` method to only set the created time if the `created` property has a value. ```language-php public function getPost(): NodeInterface { $post = Node::create([ 'title' => $this->title, 'type' => 'post', ]); if ($this->created !== NULL) { $post->setCreatedTime($this->created->getTimestamp()); } $post->save(); return $post; } ``` Now, we can see a similar error to the one before for `isNotPublished()`. > Error: Call to undefined method Drupal\atdc\Builder\PostBuilder::isNotPublished() Again, create the simplest version of the method so the test can progress: ```language-php public function isNotPublished(): self { return $this; } Now, you should get the error you were likely expecting: > The text "Post two" appears in the text of this page, but it should not. As we've set post two to be unpublished, we don't want it to be displayed. However, we have no logic for that. ## Updating PostBuilder Within `PostBuilder`, we need to use the `isPublished` and `isNotPublished` methods to set the status of the node that's building built. First, add an `isPublished` property to the class and set it to be `TRUE` by default: ```language-php private bool $isPublished = TRUE; ``` Next, update the `isPublished()` and `isNotPublished()` methods to set the value appropriately: ```language-php public function isNotPublished(): self { $this->isPublished = FALSE; return $this; } public function isPublished(): self { $this->isPublished = TRUE; return $this; } ``` Even though `isPublished` is already true by default, doing this makes it explicit and makes what's being tested clearer. Finally, within `getPost()`, update the code that creates the node to set the `status` property accordingly. ```language-php $post = Node::create([ 'status' => $this->isPublished, 'title' => $this->title, 'type' => 'post', ]); ``` With these changes, the nodes have the correct status, but the test is still failing. ## Updating PostNodeRepository We also need to update the `PostNodeRepository` as that is responsible for loading and returning the relevant nodes from the database. Currently, all we're doing is this: ```language-php $nodes = $nodeStorage->loadMultiple(); ``` This will load all nodes, regardless of their type or status. To fix this, change this to use `loadByProperties()` instead: ```language-php $nodes = $nodeStorage->loadByProperties(); ``` `loadByProperties()` allows you to pass an array of properties and values to filter the results. Note: you can also use `->getQuery()` if you prefer and write the query yourself. For this case, let's add a property for `status` and its value to be `TRUE`: ```language-php $nodes = $nodeStorage->loadByProperties([ 'status' => TRUE, ]); ``` This ensures that only published nodes are returned, so the unpublished nodes are no longer shown, and the tests pass. ## Only returning posts The other issue is all published nodes are returned, even if they aren't posts. Before adding this to `PostNodeRepository`, create a new failing test for it: ```language-php public function testOnlyPostNodesAreShown(): void { PostBuilder::create()->setTitle('Post one')->getPost(); PostBuilder::create()->setTitle('Post two')->getPost(); $this->createNode([ 'title' => 'This is not a post', 'type' => 'page', ]); $this->drupalGet('/blog'); $assert = $this->assertSession(); $assert->pageTextContains('Post one'); $assert->pageTextContains('Post two'); $assert->pageTextNotContains('This is not a post'); } ``` Use `PostBuilder` to create two posts and `$this->createNode()` to create a post of a different type. In this test, we want the two post titles to be shown but not the page's title. If you run the test, it should fail as expected: > The text "This is not a post" appears in the text of this page, but it should not. Now we have a failing test, let's add the extra condition to `PostNodeRepository`: ```language-php $nodes = $nodeStorage->loadByProperties([ 'status' => TRUE, 'type' => 'post', ]); ``` With both conditions, both tests should now pass, and you should only see published node articles on your blog page. ## Conclusion With these changes, the `PostNodeRepository` is more robust and fully featured. While we could also write new Kernel tests for this functionality, it's already covered in the Functional tests. If you write accompanying Kernel tests, you wouldn't be able to make them fail without also making the Functional tests fail. If you want to add them, you can. It's up to you and your project team. {% endblock %}