Compare commits
No commits in common. "8.x-1.x" and "1-fail" have entirely different histories.
8 changed files with 25 additions and 383 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
composer.lock
|
|
37
README.md
37
README.md
|
@ -1,31 +1,16 @@
|
||||||
# TDD Example Drupal 8 Blog Module
|
# TDD Dublin demo module
|
||||||
|
|
||||||
A demo module to accompany my [TDD - Test Driven Drupal][0] talk, originally for DrupalCamp
|
A demo module to accompany my [TDD Test Driven Drupal][0] talk at DrupalCamp
|
||||||
Dublin 2017.
|
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
|
## Acceptance Criteria
|
||||||
|
|
||||||
This module will be used to demonstrate how to take a test-driven approach to
|
This module will be used to demonstrate how to take a test-driven approach to
|
||||||
develop a module to the following acceptance criteria:
|
develop a module to the following acceptance criteria:
|
||||||
|
|
||||||
- As a site visitor
|
- As a site visitor
|
||||||
- I want to see a list of all published blog posts at `/blog`
|
- I want to see a list of all published pages at `/pages`
|
||||||
- Ordered by post date, with the newest posts first
|
- Ordered alphabetically by title
|
||||||
|
|
||||||
## 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
|
## Running the Tests
|
||||||
|
|
||||||
|
@ -33,23 +18,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).
|
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.
|
The path to your `vendor` directory may be different depending on your setup.
|
||||||
|
|
||||||
Because of autoloading, you will either need to be inside Drupal's `core` subdirectory
|
Because of autoloading, you will 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.
|
when running the tests for them to execute successfully.
|
||||||
|
|
||||||
This also assumes that the module is within a `modules/custom` directory and
|
This also assumes that the module is within a `modules/custom` directory and
|
||||||
named `tdd_blog` as per the repository name.
|
named `tdd_dublin` as per the repository name.
|
||||||
|
|
||||||
```
|
```
|
||||||
vendor/bin/phpunit -c core modules/custom/tdd_blog
|
cd core
|
||||||
|
|
||||||
|
../vendor/bin/phpunit ../modules/custom/tdd_dublin
|
||||||
```
|
```
|
||||||
|
|
||||||
You can use PHPUnit's `--filter` option to specify a single test method to run,
|
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:
|
rather than all of the tests within the module. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
vendor/bin/phpunit -c core modules/custom/tdd_blog --filter=testOnlyPublishedPagesAreShown
|
../vendor/bin/phpunit ../modules/custom/tdd_dublin --filter=testOnlyPublishedPagesAreShown
|
||||||
```
|
```
|
||||||
|
|
||||||
[0]: https://www.oliverdavies.uk/talks/tdd-test-driven-drupal
|
[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
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "drupal/tdd_blog",
|
|
||||||
"type": "drupal-custom-module"
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
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,228 +0,0 @@
|
||||||
langcode: en
|
|
||||||
status: true
|
|
||||||
dependencies:
|
|
||||||
config:
|
|
||||||
- node.type.article
|
|
||||||
module:
|
|
||||||
- node
|
|
||||||
- user
|
|
||||||
id: blog
|
|
||||||
label: Blog
|
|
||||||
module: views
|
|
||||||
description: ''
|
|
||||||
tag: ''
|
|
||||||
base_table: node_field_data
|
|
||||||
base_field: nid
|
|
||||||
core: 8.x
|
|
||||||
display:
|
|
||||||
default:
|
|
||||||
display_plugin: default
|
|
||||||
id: default
|
|
||||||
display_title: Master
|
|
||||||
position: 0
|
|
||||||
display_options:
|
|
||||||
access:
|
|
||||||
type: perm
|
|
||||||
options:
|
|
||||||
perm: 'access content'
|
|
||||||
cache:
|
|
||||||
type: tag
|
|
||||||
options: { }
|
|
||||||
query:
|
|
||||||
type: views_query
|
|
||||||
options:
|
|
||||||
disable_sql_rewrite: false
|
|
||||||
distinct: false
|
|
||||||
replica: false
|
|
||||||
query_comment: ''
|
|
||||||
query_tags: { }
|
|
||||||
exposed_form:
|
|
||||||
type: basic
|
|
||||||
options:
|
|
||||||
submit_button: Apply
|
|
||||||
reset_button: false
|
|
||||||
reset_button_label: Reset
|
|
||||||
exposed_sorts_label: 'Sort by'
|
|
||||||
expose_sort_order: true
|
|
||||||
sort_asc_label: Asc
|
|
||||||
sort_desc_label: Desc
|
|
||||||
pager:
|
|
||||||
type: mini
|
|
||||||
options:
|
|
||||||
items_per_page: 10
|
|
||||||
offset: 0
|
|
||||||
id: 0
|
|
||||||
total_pages: null
|
|
||||||
expose:
|
|
||||||
items_per_page: false
|
|
||||||
items_per_page_label: 'Items per page'
|
|
||||||
items_per_page_options: '5, 10, 25, 50'
|
|
||||||
items_per_page_options_all: false
|
|
||||||
items_per_page_options_all_label: '- All -'
|
|
||||||
offset: false
|
|
||||||
offset_label: Offset
|
|
||||||
tags:
|
|
||||||
previous: ‹‹
|
|
||||||
next: ››
|
|
||||||
style:
|
|
||||||
type: default
|
|
||||||
options:
|
|
||||||
grouping: { }
|
|
||||||
row_class: ''
|
|
||||||
default_row_class: true
|
|
||||||
uses_fields: false
|
|
||||||
row:
|
|
||||||
type: fields
|
|
||||||
options:
|
|
||||||
inline: { }
|
|
||||||
separator: ''
|
|
||||||
hide_empty: false
|
|
||||||
default_field_elements: true
|
|
||||||
fields:
|
|
||||||
title:
|
|
||||||
id: title
|
|
||||||
table: node_field_data
|
|
||||||
field: title
|
|
||||||
entity_type: node
|
|
||||||
entity_field: title
|
|
||||||
label: ''
|
|
||||||
alter:
|
|
||||||
alter_text: false
|
|
||||||
make_link: false
|
|
||||||
absolute: false
|
|
||||||
trim: false
|
|
||||||
word_boundary: false
|
|
||||||
ellipsis: false
|
|
||||||
strip_tags: false
|
|
||||||
html: false
|
|
||||||
hide_empty: false
|
|
||||||
empty_zero: false
|
|
||||||
settings:
|
|
||||||
link_to_entity: true
|
|
||||||
plugin_id: field
|
|
||||||
relationship: none
|
|
||||||
group_type: group
|
|
||||||
admin_label: ''
|
|
||||||
exclude: false
|
|
||||||
element_type: ''
|
|
||||||
element_class: ''
|
|
||||||
element_label_type: ''
|
|
||||||
element_label_class: ''
|
|
||||||
element_label_colon: true
|
|
||||||
element_wrapper_type: ''
|
|
||||||
element_wrapper_class: ''
|
|
||||||
element_default_classes: true
|
|
||||||
empty: ''
|
|
||||||
hide_alter_empty: true
|
|
||||||
click_sort_column: value
|
|
||||||
type: string
|
|
||||||
group_column: value
|
|
||||||
group_columns: { }
|
|
||||||
group_rows: true
|
|
||||||
delta_limit: 0
|
|
||||||
delta_offset: 0
|
|
||||||
delta_reversed: false
|
|
||||||
delta_first_last: false
|
|
||||||
multi_type: separator
|
|
||||||
separator: ', '
|
|
||||||
field_api_classes: false
|
|
||||||
filters:
|
|
||||||
status:
|
|
||||||
value: '1'
|
|
||||||
table: node_field_data
|
|
||||||
field: status
|
|
||||||
plugin_id: boolean
|
|
||||||
entity_type: node
|
|
||||||
entity_field: status
|
|
||||||
id: status
|
|
||||||
expose:
|
|
||||||
operator: ''
|
|
||||||
group: 1
|
|
||||||
type:
|
|
||||||
id: type
|
|
||||||
table: node_field_data
|
|
||||||
field: type
|
|
||||||
relationship: none
|
|
||||||
group_type: group
|
|
||||||
admin_label: ''
|
|
||||||
operator: in
|
|
||||||
value:
|
|
||||||
article: article
|
|
||||||
group: 1
|
|
||||||
exposed: false
|
|
||||||
expose:
|
|
||||||
operator_id: ''
|
|
||||||
label: ''
|
|
||||||
description: ''
|
|
||||||
use_operator: false
|
|
||||||
operator: ''
|
|
||||||
identifier: ''
|
|
||||||
required: false
|
|
||||||
remember: false
|
|
||||||
multiple: false
|
|
||||||
remember_roles:
|
|
||||||
authenticated: authenticated
|
|
||||||
reduce: false
|
|
||||||
is_grouped: false
|
|
||||||
group_info:
|
|
||||||
label: ''
|
|
||||||
description: ''
|
|
||||||
identifier: ''
|
|
||||||
optional: true
|
|
||||||
widget: select
|
|
||||||
multiple: false
|
|
||||||
remember: false
|
|
||||||
default_group: All
|
|
||||||
default_group_multiple: { }
|
|
||||||
group_items: { }
|
|
||||||
entity_type: node
|
|
||||||
entity_field: type
|
|
||||||
plugin_id: bundle
|
|
||||||
sorts:
|
|
||||||
created:
|
|
||||||
id: created
|
|
||||||
table: node_field_data
|
|
||||||
field: created
|
|
||||||
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: { }
|
|
||||||
relationships: { }
|
|
||||||
arguments: { }
|
|
||||||
display_extenders: { }
|
|
||||||
cache_metadata:
|
|
||||||
max-age: -1
|
|
||||||
contexts:
|
|
||||||
- 'languages:language_content'
|
|
||||||
- 'languages:language_interface'
|
|
||||||
- url.query_args
|
|
||||||
- 'user.node_grants:view'
|
|
||||||
- user.permissions
|
|
||||||
tags: { }
|
|
||||||
page_1:
|
|
||||||
display_plugin: page
|
|
||||||
id: page_1
|
|
||||||
display_title: Page
|
|
||||||
position: 1
|
|
||||||
display_options:
|
|
||||||
display_extenders: { }
|
|
||||||
path: blog
|
|
||||||
cache_metadata:
|
|
||||||
max-age: -1
|
|
||||||
contexts:
|
|
||||||
- 'languages:language_content'
|
|
||||||
- 'languages:language_interface'
|
|
||||||
- url.query_args
|
|
||||||
- 'user.node_grants:view'
|
|
||||||
- user.permissions
|
|
||||||
tags: { }
|
|
|
@ -1,9 +1,4 @@
|
||||||
name: 'TDD Blog'
|
name: 'TDD Dublin'
|
||||||
description: 'A demo module for DrupalCamp Dublin to show test driven module development.'
|
description: 'A demo module for DrupalCamp Dublin to show test driven module development.'
|
||||||
core: 8.x
|
core: 8.x
|
||||||
core_version_requirement: ^8 || ^9
|
|
||||||
type: module
|
type: module
|
||||||
|
|
||||||
dependencies:
|
|
||||||
- drupal:node
|
|
||||||
- drupal:views
|
|
|
@ -1,20 +1,24 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Drupal\Tests\tdd_blog\Functional;
|
namespace Drupal\Tests\tdd_dublin\Functional;
|
||||||
|
|
||||||
use Drupal\Tests\BrowserTestBase;
|
use Drupal\Tests\BrowserTestBase;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class PageListTest extends BrowserTestBase {
|
class PageListTest extends BrowserTestBase {
|
||||||
|
|
||||||
protected static $modules = ['tdd_blog'];
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['tdd_dublin'];
|
||||||
|
|
||||||
protected $defaultTheme = 'stark';
|
/**
|
||||||
|
* Test that the pages listing page exists and is accessible.
|
||||||
public function testBlogPageExists() {
|
*/
|
||||||
$this->drupalGet('blog');
|
public function testListingPageExists() {
|
||||||
|
// Go to /pages and check that it is accessible by checking the status
|
||||||
$this->assertSession()->statusCodeEquals(Response::HTTP_OK);
|
// code.
|
||||||
|
$this->drupalGet('pages');
|
||||||
|
$this->assertSession()->statusCodeEquals(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
<?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