659 lines
19 KiB
Markdown
659 lines
19 KiB
Markdown
|
---
|
|||
|
title: Writing a new Drupal 8 Module using Test Driven Development (TDD)
|
|||
|
date: 2017-11-07
|
|||
|
tags: [drupal, testing, tdd, simpletest, phpunit]
|
|||
|
excerpt:
|
|||
|
How to write automated tests and follow test driven development for Drupal
|
|||
|
modules.
|
|||
|
meta:
|
|||
|
image:
|
|||
|
url: /images/talks/test-driven-drupal-development.png
|
|||
|
width: 2560
|
|||
|
height: 1440
|
|||
|
type: image/png
|
|||
|
promoted: true
|
|||
|
---
|
|||
|
|
|||
|
<p class="text-center" markdown="1">![](/images/blog/drupalcamp-dublin.jpg)</p>
|
|||
|
|
|||
|
I recently gave a [talk on automated testing in Drupal][0] talk at [DrupalCamp
|
|||
|
Dublin][1] and as a lunch and learn session for my colleagues at Microserve. As
|
|||
|
part of the talk, I gave an example of how to build a Drupal 8 module using a
|
|||
|
test driven approach. I’ve released the [module code on GitHub][2], and this
|
|||
|
post outlines the steps of the process.
|
|||
|
|
|||
|
## Prerequisites
|
|||
|
|
|||
|
You have created a `core/phpunit.xml` file based on `core/phpunit.xml.dist`, and
|
|||
|
populated it with your database credentials so that PHPUnit can bootstrap the
|
|||
|
Drupal database as part of the tests. [Here is an example][5].
|
|||
|
|
|||
|
## Acceptance Criteria
|
|||
|
|
|||
|
For the module, we are going to satisfy this example acceptance criteria:
|
|||
|
|
|||
|
> As a site visitor,<br> I want to see all published pages at /pages<br> Ordered
|
|||
|
> alphabetically by title
|
|||
|
|
|||
|
## Initial Setup
|
|||
|
|
|||
|
Let’s start by writing the minimal code needed in order for the new module to be
|
|||
|
enabled. In Drupal 8, this is the `.info.yml` file.
|
|||
|
|
|||
|
```language-yaml
|
|||
|
# tdd_dublin.info.yml
|
|||
|
|
|||
|
name: 'TDD Dublin'
|
|||
|
excerpt: 'A demo module for DrupalCamp Dublin to show test driven module development.'
|
|||
|
core: 8.x
|
|||
|
type: module
|
|||
|
```
|
|||
|
|
|||
|
We can also add the test file structure at this point too. We’ll call it
|
|||
|
`PageTestTest.php` and put it within a `tests/src/Functional` directory. As this
|
|||
|
is a functional test, it extends the `BrowserTestBase` class, and we need to
|
|||
|
ensure that the tdd_dublin module is enabled by adding it to the `$modules`
|
|||
|
array.
|
|||
|
|
|||
|
```language-php
|
|||
|
// tests/src/Functional/PageListTest.php
|
|||
|
|
|||
|
namespace Drupal\Tests\tdd_dublin\Functional;
|
|||
|
|
|||
|
use Drupal\Tests\BrowserTestBase\BrowserTestBase;
|
|||
|
|
|||
|
class PageListTest extends BrowserTestBase {
|
|||
|
|
|||
|
protected static $modules = ['tdd_dublin'];
|
|||
|
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
With this in place, we can now start adding test methods.
|
|||
|
|
|||
|
## Ensure that the Listing page Exists
|
|||
|
|
|||
|
### Writing the First Test
|
|||
|
|
|||
|
Let’s start by testing that the listing page exists at /pages. We can do this by
|
|||
|
loading the page and checking the status code. If the page exists, the code will
|
|||
|
be 200, otherwise it will be 404.
|
|||
|
|
|||
|
I usually like to write comments first within the test method, just to outline
|
|||
|
the steps that I'm going to take and then replace it with code.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testListingPageExists() {
|
|||
|
// Go to /pages and check that it is accessible by checking the status
|
|||
|
// code.
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We can use the `drupalGet()` method to browse to the required path, i.e.
|
|||
|
`/pages`, and then write an assertion for the response code value.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testListingPageExists() {
|
|||
|
$this->drupalGet('pages');
|
|||
|
|
|||
|
$this->assertSession()->statusCodeEquals(200);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Running the Test
|
|||
|
|
|||
|
In order to run the tests, you either need to include `-c core` or be inside the
|
|||
|
`core` directory when running the command, to ensure that the test classes are
|
|||
|
autoloaded so can be found, though the path to the `vendor` directory may be
|
|||
|
different depending on your project structure. You can also specify a path
|
|||
|
within which to run the tests - e.g. within the module’s `test` directory.
|
|||
|
|
|||
|
```language-plain
|
|||
|
$ vendor/bin/phpunit -c core modules/custom/tdd_dublin/tests
|
|||
|
```
|
|||
|
|
|||
|
<div class="note" markdown="1">
|
|||
|
Note: I’m using Docksal, and I’ve noticed that I need to run the tests from within the CLI container. You can do this by running the `fin bash` command.
|
|||
|
</div>
|
|||
|
|
|||
|
```language-plain
|
|||
|
1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists
|
|||
|
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
|
|||
|
|
|||
|
FAILURES!
|
|||
|
Tests: 1, Assertions: 1, Errors: 1.
|
|||
|
```
|
|||
|
|
|||
|
Because the route does not yet exist, the response code returned is 404, so the
|
|||
|
test fails.
|
|||
|
|
|||
|
Now we can make it pass by adding the page. For this, I will use the Views
|
|||
|
module, though you could achieve the same result with a custom route and a
|
|||
|
Controller.
|
|||
|
|
|||
|
### Building the View
|
|||
|
|
|||
|
To begin with, I will create a view showing all types of content with a default
|
|||
|
sort order of newest first. We will use further tests to ensure that only the
|
|||
|
correct content is returned and that it is ordered correctly.
|
|||
|
|
|||
|
![](/images/blog/tdd-drupal-1.png) { .with-border }
|
|||
|
|
|||
|
The only addition I will make to the view is to add a path at `pages`, as per
|
|||
|
the acceptance criteria.
|
|||
|
|
|||
|
![](/images/blog/tdd-drupal-2.png) { .with-border }
|
|||
|
|
|||
|
### Exporting the View
|
|||
|
|
|||
|
With the first version of the view built, it needs to be incldued within the
|
|||
|
module so that it can be enabled when the test is run. To do this, we need to
|
|||
|
export the configuration for the view, and place it within the module’s
|
|||
|
`config/install` directory. This can be done using the `drush config-export`
|
|||
|
command or from within the Drupal UI. In either case, the `uid` line at the top
|
|||
|
of the file needs to be removed so the configuration can be installed.
|
|||
|
|
|||
|
Here is the exported view configuration:
|
|||
|
|
|||
|
```language-yaml
|
|||
|
langcode: en
|
|||
|
status: true
|
|||
|
dependencies:
|
|||
|
module:
|
|||
|
- node
|
|||
|
- user
|
|||
|
id: pages
|
|||
|
label: pages
|
|||
|
module: views
|
|||
|
excerpt: ''
|
|||
|
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
|
|||
|
sorts:
|
|||
|
created:
|
|||
|
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: ''
|
|||
|
exposed: false
|
|||
|
expose:
|
|||
|
label: ''
|
|||
|
granularity: second
|
|||
|
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: pages
|
|||
|
cache_metadata:
|
|||
|
max-age: -1
|
|||
|
contexts:
|
|||
|
- 'languages:language_content'
|
|||
|
- 'languages:language_interface'
|
|||
|
- url.query_args
|
|||
|
- 'user.node_grants:view'
|
|||
|
- user.permissions
|
|||
|
tags: { }
|
|||
|
```
|
|||
|
|
|||
|
When the test is run again, we see a different error that leads us to the next
|
|||
|
step.
|
|||
|
|
|||
|
```language-plain
|
|||
|
1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists
|
|||
|
Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by <em class="placeholder">tdd_dublin</em> have unmet dependencies: <em class="placeholder">node.type.page (node), views.view.pages (node, views)</em>
|
|||
|
|
|||
|
FAILURES!
|
|||
|
Tests: 1, Assertions: 0, Errors: 1.
|
|||
|
```
|
|||
|
|
|||
|
This error is identifying unmet dependencies within the module’s configuration.
|
|||
|
In this case, the view that we’ve added depends on the node and views modules,
|
|||
|
but these aren’t enabled. To fix this, we can add the extra modules as
|
|||
|
dependencies of tdd_dublin so they will be enabled too.
|
|||
|
|
|||
|
```language-yaml
|
|||
|
# tdd_dublin.info.yml
|
|||
|
|
|||
|
dependencies:
|
|||
|
- drupal:node
|
|||
|
- drupal:views
|
|||
|
```
|
|||
|
|
|||
|
```language-plain
|
|||
|
1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists
|
|||
|
Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by <em class="placeholder">tdd_dublin</em> have unmet dependencies: <em class="placeholder">views.view.pages (node.type.page)</em>
|
|||
|
|
|||
|
FAILURES!
|
|||
|
Tests: 1, Assertions: 0, Errors: 1.
|
|||
|
```
|
|||
|
|
|||
|
With the modules enabled, we can see one more unmet dependency for
|
|||
|
`node.type.page`. This means that we need a page content type to be able to
|
|||
|
install the view. We can fix this in the same way as before, by exporting the
|
|||
|
configuration and copying it into the `config/install` directory.
|
|||
|
|
|||
|
With this in place, the test should now pass - and it does.
|
|||
|
|
|||
|
```language-plain
|
|||
|
Time: 26.04 seconds, Memory: 6.00MB
|
|||
|
|
|||
|
OK (1 test, 1 assertion)
|
|||
|
```
|
|||
|
|
|||
|
We now have a test to ensure that the listing page exists.
|
|||
|
|
|||
|
## Ensure that only Published Pages are Shown
|
|||
|
|
|||
|
### Writing the Test
|
|||
|
|
|||
|
Now that we have a working page, we can now move on to checking that the correct
|
|||
|
content is returned. Again, I’ll start by writing comments and then translate
|
|||
|
that into code.
|
|||
|
|
|||
|
The objectives of this test are:
|
|||
|
|
|||
|
- To ensure that only page nodes are returned.
|
|||
|
- To ensure that only published nodes are returned.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testOnlyPublishedPagesAreShown() {
|
|||
|
// Given that a have a mixture of published and unpublished pages, as well
|
|||
|
// as other types of content.
|
|||
|
|
|||
|
// When I view the page.
|
|||
|
|
|||
|
// Then I should only see the published pages.
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
In order to test the different scenarios, I will create an additional "article"
|
|||
|
content type, create a node of this type as well as one published and one
|
|||
|
unpublished page. From this combination, I only expect one node to be visible.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testOnlyPublishedPagesAreShown() {
|
|||
|
$this->drupalCreateContentType(['type' => 'article']);
|
|||
|
|
|||
|
$this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
|
|||
|
$this->drupalCreateNode(['type' => 'article']);
|
|||
|
$this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
|
|||
|
|
|||
|
// When I view the page.
|
|||
|
|
|||
|
// Then I should only see the published pages.
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We could use `drupalGet()` again to browse to the page and write assertions
|
|||
|
based on the rendered HTML, though I’d rather do this against the data returned
|
|||
|
from the view itself. This is so that the test isn’t too tightly coupled to the
|
|||
|
presentation logic, and we won’t be in a situation where at a later date the
|
|||
|
test fails because of changes made to how the data is displayed.
|
|||
|
|
|||
|
Rather, I’m going to use `views_get_view_result()` to programmatically get the
|
|||
|
result of the view. This returns an array of `Drupal\views\ResultRow` objects,
|
|||
|
which contain the nodes. I can use `array_column` to extract the node IDs from
|
|||
|
the view result into an array.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testOnlyPublishedPagesAreShown() {
|
|||
|
$this->drupalCreateContentType(['type' => 'article']);
|
|||
|
|
|||
|
$this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
|
|||
|
$this->drupalCreateNode(['type' => 'article']);
|
|||
|
$this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
|
|||
|
|
|||
|
$result = views_get_view_result('pages');
|
|||
|
$nids = array_column($result, 'nid');
|
|||
|
|
|||
|
// Then I should only see the published pages.
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
From the generated nodes, I can use `assertEquals()` to compare the returned
|
|||
|
node IDs from the view against an array of expected node IDs - in this case, I
|
|||
|
expect only node 1 to be returned.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testOnlyPublishedPagesAreShown() {
|
|||
|
$this->drupalCreateContentType(['type' => 'article']);
|
|||
|
|
|||
|
$this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
|
|||
|
$this->drupalCreateNode(['type' => 'article']);
|
|||
|
$this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
|
|||
|
|
|||
|
$result = views_get_view_result('pages');
|
|||
|
$nids = array_column($result, 'nid');
|
|||
|
|
|||
|
$this->assertEquals([1], $nids);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Running the Test
|
|||
|
|
|||
|
The test fails as no extra conditions have been added to the view, though the
|
|||
|
default "Content: Published" filter is already excluding one of the page nodes.
|
|||
|
We can see from the output from the test that node 1 (a page) and node 2 (the
|
|||
|
article) are both being returned.
|
|||
|
|
|||
|
```language-plain
|
|||
|
1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testOnlyPublishedPagesAreShown
|
|||
|
Failed asserting that two arrays are equal.
|
|||
|
--- Expected
|
|||
|
+++ Actual
|
|||
|
@@ @@
|
|||
|
Array (
|
|||
|
- 0 => 1
|
|||
|
+ 0 => '2'
|
|||
|
+ 1 => '1'
|
|||
|
)
|
|||
|
|
|||
|
FAILURES!
|
|||
|
Tests: 1, Assertions: 3, Failures: 1.
|
|||
|
```
|
|||
|
|
|||
|
### Updating the Test
|
|||
|
|
|||
|
We can fix this by adding another condition to the view, to only show content
|
|||
|
based on the node type - i.e. only return page nodes.
|
|||
|
|
|||
|
![](/images/blog/tdd-drupal-3.png) { .with-border }
|
|||
|
|
|||
|
Once the view is updated and the configuration is updated within the module, the
|
|||
|
test should then pass - and it does.
|
|||
|
|
|||
|
```language-plain
|
|||
|
Time: 24.76 seconds, Memory: 6.00MB
|
|||
|
|
|||
|
OK (1 test, 3 assertions)
|
|||
|
```
|
|||
|
|
|||
|
## Ensure that the Pages are in the Correct Order
|
|||
|
|
|||
|
### Writing the Test
|
|||
|
|
|||
|
As we know that the correct content is being returned, we can now focus on
|
|||
|
displaying it in the correct order. We’ll start again by adding a new test
|
|||
|
method and filling out the comments.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testResultsAreOrderedAlphabetically() {
|
|||
|
// Given I have multiple nodes with different titles.
|
|||
|
|
|||
|
// When I view the pages list.
|
|||
|
|
|||
|
// Then I should see pages in the correct order.
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
To begin with this time, I’ll create a number of different nodes and specify the
|
|||
|
title for each. These are intentionally in the incorrect order alphabetically so
|
|||
|
that we can see the test fail initially and then see it pass after making a
|
|||
|
change so we know that the change worked.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testResultsAreOrderedAlphabetically() {
|
|||
|
$this->drupalCreateNode(['title' => 'Page A']);
|
|||
|
$this->drupalCreateNode(['title' => 'Page D']);
|
|||
|
$this->drupalCreateNode(['title' => 'Page C']);
|
|||
|
$this->drupalCreateNode(['title' => 'Page B']);
|
|||
|
|
|||
|
// When I view the pages list.
|
|||
|
|
|||
|
// Then I should see pages in the correct order.
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We can use the same method as the previous test to get the returned IDs, using
|
|||
|
`views_get_view_result()` and `array_column()`, and assert that the returned
|
|||
|
node IDs match the expected node IDs in the specified order. Based on the
|
|||
|
defined titles, the order should be 1, 4, 3, 2.
|
|||
|
|
|||
|
```language-php
|
|||
|
public function testResultsAreOrderedAlphabetically() {
|
|||
|
$this->drupalCreateNode(['title' => 'Page A']);
|
|||
|
$this->drupalCreateNode(['title' => 'Page D']);
|
|||
|
$this->drupalCreateNode(['title' => 'Page C']);
|
|||
|
$this->drupalCreateNode(['title' => 'Page B']);
|
|||
|
|
|||
|
$nids = array_column(views_get_view_result('pages'), 'nid');
|
|||
|
|
|||
|
$this->assertEquals([1, 4, 3, 2], $nids);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Running the Test
|
|||
|
|
|||
|
As expected the test fails, as the default sort criteria in the view orders the
|
|||
|
results by their created date.
|
|||
|
|
|||
|
In the test output, we can see the returned results are in sequential order so
|
|||
|
the results array does not match the expected one.
|
|||
|
|
|||
|
This would be particularly more complicated to test if I was using `drupalGet()`
|
|||
|
and having to parse the HTML, compared to getting the results as an array from
|
|||
|
the view programmatically.
|
|||
|
|
|||
|
```language-plain
|
|||
|
1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testResultsAreOrderedAlphabetically
|
|||
|
Failed asserting that two arrays are equal.
|
|||
|
--- Expected
|
|||
|
+++ Actual
|
|||
|
@@ @@
|
|||
|
Array (
|
|||
|
- 0 => 1
|
|||
|
- 1 => 4
|
|||
|
- 2 => 3
|
|||
|
- 3 => 2
|
|||
|
+ 0 => '1'
|
|||
|
+ 1 => '2'
|
|||
|
+ 2 => '3'
|
|||
|
+ 3 => '4'
|
|||
|
)
|
|||
|
|
|||
|
FAILURES!
|
|||
|
Tests: 1, Assertions: 2, Failures: 1.
|
|||
|
```
|
|||
|
|
|||
|
### Updating the Test
|
|||
|
|
|||
|
This can be fixed by removing the default sort criteria and adding a new one
|
|||
|
based on "Content: Title".
|
|||
|
|
|||
|
![](/images/blog/tdd-drupal-4.png) { .with-border }
|
|||
|
|
|||
|
Again, once the view has been updated and exported, the test should pass - and
|
|||
|
it does.
|
|||
|
|
|||
|
```language-plain
|
|||
|
Time: 27.55 seconds, Memory: 6.00MB
|
|||
|
|
|||
|
OK (1 test, 2 assertions)
|
|||
|
```
|
|||
|
|
|||
|
## Ensure all Tests Still Pass
|
|||
|
|
|||
|
Now we know that all the tests pass individually, all of the module tests should
|
|||
|
now be run to ensure that they all still pass and that there have been no
|
|||
|
regressions due to any of the changes.
|
|||
|
|
|||
|
```language-plain
|
|||
|
docker@cli:/var/www$ vendor/bin/phpunit -c core modules/custom/tdd_dublin/tests
|
|||
|
|
|||
|
Testing modules/custom/tdd_dublin/tests
|
|||
|
...
|
|||
|
|
|||
|
Time: 1.27 minutes, Memory: 6.00MB
|
|||
|
|
|||
|
OK (3 tests, 6 assertions)
|
|||
|
```
|
|||
|
|
|||
|
They all pass, so we be confident that the code works as expected, we can
|
|||
|
continue to refactor if needed, and if any changes are made to this module at a
|
|||
|
later date, we have the tests to ensure that any regressions are caught and
|
|||
|
fixed before deployment.
|
|||
|
|
|||
|
## Next Steps
|
|||
|
|
|||
|
I’ve started looking into whether some of the tests can be rewritten as kernel
|
|||
|
tests, which should result in quicker test execution. I will post any updated
|
|||
|
code to the [GitHub repository][3], and will also do another blog post
|
|||
|
highlighting the differences between functional and kernel tests and the steps
|
|||
|
taken to do the conversion.
|
|||
|
|
|||
|
[0]: {{site.url}}/talks/tdd-test-driven-drupal
|
|||
|
[1]: http://2017.drupal.ie
|
|||
|
[2]: https://github.com/opdavies/tdd_dublin
|
|||
|
[3]: https://packagist.org/packages/tightenco/collect
|
|||
|
[4]: http://php.net/manual/en/function.array-column.php
|
|||
|
[5]: https://gist.github.com/opdavies/dc5f0cea46ccd349b34a9f3a463c14bb
|