Compare commits
28 commits
Author | SHA1 | Date | |
---|---|---|---|
bf1fce2a52 | |||
8d0c16dd8e | |||
dcab1aca9a | |||
52f13fcac8 | |||
772d3d31b0 | |||
ccaa426a38 | |||
![]() |
5be880e496 | ||
![]() |
dccc8f1875 | ||
![]() |
f8a864b6cc | ||
![]() |
41bcbb50ab | ||
![]() |
442ec19bc1 | ||
![]() |
4ec57cdf2e | ||
![]() |
38314a0ed2 | ||
![]() |
238dd8fb68 | ||
![]() |
4238580d66 | ||
![]() |
349ec8f965 | ||
81cb121a99 | |||
ab524316b6 | |||
eb5465c5a6 | |||
af09babd7a | |||
5574c5ee9a | |||
5ead634ab2 | |||
66389b7a21 | |||
81af444a97 | |||
461e66bf53 | |||
992390b803 | |||
fc8eb51bdf | |||
569d4a80dc |
8 changed files with 169 additions and 91 deletions
37
README.md
37
README.md
|
@ -1,16 +1,31 @@
|
|||
# TDD Dublin demo module
|
||||
# TDD Example Drupal 8 Blog Module
|
||||
|
||||
A demo module to accompany my [TDD Test Driven Drupal][0] talk at DrupalCamp
|
||||
A demo module to accompany my [TDD - Test Driven Drupal][0] talk, originally for DrupalCamp
|
||||
Dublin 2017.
|
||||
|
||||
In order to see my workflow of writing comments first, converting them into
|
||||
failing tests, and then writing the implementation code to make them pass, you
|
||||
can see the [list of previous commits][1] and see each step taken, as well as
|
||||
[the tags][2] that identify the commits when each failing test is added and
|
||||
then subsequently passes.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
This module will be used to demonstrate how to take a test-driven approach to
|
||||
develop a module to the following acceptance criteria:
|
||||
|
||||
- As a site visitor
|
||||
- I want to see a list of all published pages at `/pages`
|
||||
- Ordered alphabetically by title
|
||||
- I want to see a list of all published blog posts at `/blog`
|
||||
- Ordered by post date, with the newest posts first
|
||||
|
||||
## Installation
|
||||
|
||||
Within your Drupal 8 site:
|
||||
|
||||
```bash
|
||||
cd modules
|
||||
git clone git@github.com:opdavies/drupal-module-tdd-blog.git tdd_blog
|
||||
```
|
||||
|
||||
## Running the Tests
|
||||
|
||||
|
@ -18,23 +33,23 @@ These tests are functional tests based on the `BrowserTestBase` class so need
|
|||
to be executed with PHPUnit (which is required in core's `composer.json` file).
|
||||
The path to your `vendor` directory may be different depending on your setup.
|
||||
|
||||
Because of autoloading, you will need to be inside Drupal's `core` subdirectory
|
||||
when running the tests for them to execute successfully.
|
||||
Because of autoloading, you will either need to be inside Drupal's `core` subdirectory
|
||||
, or add `-c core` to the PHPUnit command when running the tests for them to execute successfully.
|
||||
|
||||
This also assumes that the module is within a `modules/custom` directory and
|
||||
named `tdd_dublin` as per the repository name.
|
||||
named `tdd_blog` as per the repository name.
|
||||
|
||||
```
|
||||
cd core
|
||||
|
||||
../vendor/bin/phpunit ../modules/custom/tdd_dublin
|
||||
vendor/bin/phpunit -c core modules/custom/tdd_blog
|
||||
```
|
||||
|
||||
You can use PHPUnit's `--filter` option to specify a single test method to run,
|
||||
rather than all of the tests within the module. For example:
|
||||
|
||||
```
|
||||
../vendor/bin/phpunit ../modules/custom/tdd_dublin --filter=testOnlyPublishedPagesAreShown
|
||||
vendor/bin/phpunit -c core modules/custom/tdd_blog --filter=testOnlyPublishedPagesAreShown
|
||||
```
|
||||
|
||||
[0]: https://www.oliverdavies.uk/talks/tdd-test-driven-drupal
|
||||
[1]: https://github.com/opdavies/drupal-module-tdd-blog/commits/HEAD
|
||||
[2]: https://github.com/opdavies/drupal-module-tdd-blog/tags
|
||||
|
|
4
composer.json
Normal file
4
composer.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "drupal/tdd_blog",
|
||||
"type": "drupal-custom-module"
|
||||
}
|
10
config/install/node.type.article.yml
Normal file
10
config/install/node.type.article.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
name: Article
|
||||
type: article
|
||||
description: 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.'
|
||||
help: ''
|
||||
new_revision: true
|
||||
preview_mode: 1
|
||||
display_submitted: true
|
|
@ -1,12 +0,0 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
_core:
|
||||
default_config_hash: KuyA4NHPXcmKAjRtwa0vQc2ZcyrUJy6IlS2TAyMNRbc
|
||||
name: 'Basic page'
|
||||
type: page
|
||||
description: 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.'
|
||||
help: ''
|
||||
new_revision: true
|
||||
preview_mode: 1
|
||||
display_submitted: false
|
|
@ -2,12 +2,12 @@ langcode: en
|
|||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.page
|
||||
- node.type.article
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: pages
|
||||
label: pages
|
||||
- node
|
||||
- user
|
||||
id: blog
|
||||
label: Blog
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
|
@ -147,7 +147,7 @@ display:
|
|||
admin_label: ''
|
||||
operator: in
|
||||
value:
|
||||
page: page
|
||||
article: article
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
|
@ -183,17 +183,17 @@ display:
|
|||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
granularity: second
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
|
@ -203,11 +203,11 @@ display:
|
|||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
|
@ -216,13 +216,13 @@ display:
|
|||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: pages
|
||||
path: blog
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
|
@ -1,6 +1,7 @@
|
|||
name: 'TDD Dublin'
|
||||
name: 'TDD Blog'
|
||||
description: 'A demo module for DrupalCamp Dublin to show test driven module development.'
|
||||
core: 8.x
|
||||
core_version_requirement: ^8 || ^9
|
||||
type: module
|
||||
|
||||
dependencies:
|
|
@ -1,59 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\tdd_dublin\Functional;
|
||||
namespace Drupal\Tests\tdd_blog\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PageListTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['tdd_dublin'];
|
||||
protected static $modules = ['tdd_blog'];
|
||||
|
||||
/**
|
||||
* Test that the pages listing page exists and is accessible.
|
||||
*/
|
||||
public function testListingPageExists() {
|
||||
// Go to /pages and check that it is accessible by checking the status
|
||||
// code.
|
||||
$this->drupalGet('pages');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Ensure that only the correct nodes are returned.
|
||||
*
|
||||
* Ensure that only published pages are returned by the view. Unpublished
|
||||
* pages or content of different types should not be shown.
|
||||
*/
|
||||
public function testOnlyPublishedPagesAreShown() {
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
public function testBlogPageExists() {
|
||||
$this->drupalGet('blog');
|
||||
|
||||
// This is a published page, so it should be visible.
|
||||
$this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
|
||||
|
||||
// This is an article, so it should not be visible.
|
||||
$this->drupalCreateNode(['type' => 'article']);
|
||||
|
||||
// This page is not published, so it should not be visible.
|
||||
$this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
|
||||
|
||||
// Rather than testing the rendered HTML, we are going to load the view
|
||||
// results programmatically and run assertions against the data it returns.
|
||||
// This makes it easier to test certain scenarios, and ensures that the
|
||||
// test is future-proofed and won't fail at a later date due to a change in
|
||||
// the presentation code.
|
||||
$result = views_get_view_result('pages');
|
||||
|
||||
// $result contains an array of Drupal\views\ResultRow objects. We can use
|
||||
// array_column to get the nid from each node and return them as an array.
|
||||
$nids = array_column($result, 'nid');
|
||||
|
||||
// Only node 1 matches the criteria of being a published page, so only that
|
||||
// node ID should be being returned from the view. assertEquals() can be
|
||||
// used to compare the expected result to what is being returned.
|
||||
$this->assertEquals([1], $nids);
|
||||
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
99
tests/src/Kernel/PageListTest.php
Normal file
99
tests/src/Kernel/PageListTest.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\tdd_blog\Kernel;
|
||||
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\views\ResultRow;
|
||||
|
||||
/**
|
||||
* @group tdd_blog
|
||||
*/
|
||||
class PageListTest extends EntityKernelTestBase {
|
||||
|
||||
use NodeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'tdd_blog',
|
||||
'views',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
$this->installConfig(['filter', 'tdd_blog']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that only the correct nodes are returned.
|
||||
*
|
||||
* Ensure that only published pages are returned by the view. Unpublished
|
||||
* pages or content of different types should not be shown.
|
||||
*/
|
||||
public function testOnlyPublishedArticlesAreShown() {
|
||||
// This is a published article, so it should be visible.
|
||||
$this->createNode(['type' => 'page', 'status' => TRUE]);
|
||||
|
||||
// This is a page, so it should not be visible.
|
||||
$this->createNode(['type' => 'article']);
|
||||
|
||||
// This article is not published, so it should not be visible.
|
||||
$this->createNode(['type' => 'article', 'status' => FALSE]);
|
||||
|
||||
// Rather than testing the rendered HTML, we are going to load the view
|
||||
// results programmatically and run assertions against the data it returns.
|
||||
// This makes it easier to test certain scenarios, and ensures that the
|
||||
// test is future-proofed and won't fail at a later date due to a change in
|
||||
// the presentation code.
|
||||
$nids = $this->getViewResults();
|
||||
|
||||
// Only node 1 matches the criteria of being a published page, so only that
|
||||
// node ID should be being returned from the view. assertEquals() can be
|
||||
// used to compare the expected result to what is being returned.
|
||||
$this->assertEquals([2], $nids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the results are ordered by title.
|
||||
*/
|
||||
public function testArticlesAreOrderedByDate() {
|
||||
$this->createNode(['type' => 'article', 'created' => (new DrupalDateTime('+1 day'))->getTimestamp()]);
|
||||
$this->createNode(['type' => 'article', 'created' => (new DrupalDateTime('+1 month'))->getTimestamp()]);
|
||||
$this->createNode(['type' => 'article', 'created' => (new DrupalDateTime('+3 days'))->getTimestamp()]);
|
||||
$this->createNode(['type' => 'article', 'created' => (new DrupalDateTime('+1 hour'))->getTimestamp()]);
|
||||
|
||||
// Get the result data from the view.
|
||||
$nids = $this->getViewResults();
|
||||
|
||||
// Compare the expected order based on the titles defined above to the
|
||||
// ordered results from the view.
|
||||
$this->assertEquals([4, 1, 3, 2], $nids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the view and get the results.
|
||||
*
|
||||
* @param string $view
|
||||
* (optional) The name of the view. Defaults to 'blog'.
|
||||
*
|
||||
* @return array
|
||||
* An array of returned entity IDs.
|
||||
*/
|
||||
private function getViewResults($view = 'blog') {
|
||||
return array_map(function (ResultRow $result) {
|
||||
return $result->_entity->id();
|
||||
}, views_get_view_result($view));
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue