oliverdavies.uk/source/_pages/atdc/7.md
2024-02-08 00:00:16 +00:00

6.5 KiB

title permalink
ATDC: Lesson 7 - Filling in old tests /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:

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:

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.

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:

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:

```php
private bool $isPublished = TRUE;

Next, update the isPublished() and isNotPublished() methods to set the value appropriately:

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.

$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:

$nodes = $nodeStorage->loadMultiple();

This will load all nodes, regardless of their type or status.

To fix this, change this to use loadByProperties() instead:

$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:

$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:

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:

$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 %}