talks/drupal-testing-workshop/2018-06-28-drupal-bristol/slides.md

38 KiB

autoscale: true build-lists: true header-emphasis: #53B0EB header: alignment(left) text: alignment(left) text-emphasis: #53B0EB theme: poster, 8

[.header: alignment(center)]

[fit] Drupal Testing Workshop

Drupal Bristol, June 2018


[.build-lists: false]

  • Module and theme developers
  • Want to know more about automated testing
  • Looking to start writing your first tests

[.background-color: #FFFFFF] [.build-lists: false] [.header: #111111] [.text: #111111, alignment(left)]

right 800%

  • Full stack Web Developer & System Administrator
  • Senior Developer at Microserve
  • Part-time freelancer
  • Acquia certified Drupal 8 Grand Master
  • Drupal 7 & 8 core contributor
  • opdavies (Drupal.org, GitHub, Twitter)
  • www.oliverdavies.uk

^ Work at Microserve. Maintain Drupal modules, PHP CLI tools and libraries Blog on my website


[.header: alignment(center)]

Test_Driven_Drupal_.com_


[.header: alignment(center)]

Why write tests?


Why write tests?

^ Dave Liddament talk - better and cheaper to catch bugs earlier (e.g. whilst developing rather than after it's been released) Refer to tests when writing implementation code ONO merge conflict


[.header: alignment(center)]

[fit] Having tests does not mean

[fit] there will be no bugs


[.header: alignment(center)]

[fit] Testing may add time now

[fit] but save more time in the future


Testing in Drupal

  • Drupal 7 - Simpletest (testing) module provided as part of core
  • Drupal 8 - PHPUnit added as a core dependency
  • PHPUnit Initiative - Simpletest to be deprecated and removed in Drupal 9

^ Focussing on PHPUnit today


Exercise 1

Local site setup


[.header: #53B0EB]

Docksal

  • Docker based local development environment
  • Microserve standard
  • Open source
  • Per site configuration and customisation
  • fin CLI, Apache, MySQL, Solr, Varnish, Mailhog, PHPMyAdmin etc
  • Virtualbox or native Docker
  • Can slow down tests
  • Provides consistency

^ Contains Drupal 8 with Composer, examples module


Exercise 2

Running Tests


Option 1

Simpletest module (UI)


fit


fit


fit


fit


fit


fit


fit


Option 2

Command line


Prerequisite (creating a phpunit.xml file)

  • Configures PHPUnit
  • Needed to run some types of tests
  • Ignored by Git by default
  • Copy core/phpunit.xml.dist to core/phpunit.xml
  • Add and change as needed
    • SIMPLETEST_BASE_URL, SIMPLETEST_DB, BROWSERTEST_OUTPUT_DIRECTORY
    • stopOnFailure="true"

cd web

../vendor/bin/phpunit -c core \
modules/contrib/examples/phpunit_example

cd web/core

../../vendor/bin/phpunit \
../modules/contrib/examples/phpunit_example

Pro-tip: Add paths to $PATH

# ~/.zshrc

export PATH=$HOME/bin:/usr/local/bin:$PATH

export PATH=vendor/bin:$PATH
export PATH=../vendor/bin:$PATH
export PATH=node_modules/.bin:$PATH

Option 2

CLI with Docksal


fin bash

cd web

../vendor/bin/phpunit -c core \
modules/contrib/examples/phpunit_example

fin bash

cd web/core

../../vendor/bin/phpunit \
../modules/contrib/examples/phpunit_example

Option 3

Docksal PHPUnit addon


  • Custom Docksal command
  • Submitted to the Docksal addons repo
  • fin addon install phpunit
  • Wrapper around phpunit command
  • Copies a stub phpunit.xml file if exists, or duplicates phpunit.xml.dist
  • Shorter command, combines two actions

^ Checks for core/phpunit.xml on each test run Will create one if is not present


fin phpunit web/modules/contrib/examples/phpunit_example

fin phpunit web/modules/contrib/examples/phpunit_example


Copying /var/www/.docksal/drupal/core/phpunit.xml to /var/www/web/core/phpunit.xml
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/contrib/examples/phpunit_example
..................................                                34 / 34 (100%)

Time: 46.8 seconds, Memory: 6.00MB

OK (34 tests, 41 assertions)

fin phpunit web/modules/contrib/examples/phpunit_example


Copying /var/www/web/core/phpunit.xml.dist to /var/www/web/core/phpunit.xml.
Please edit it's values as needed and re-run 'fin phpunit'.

fin phpunit web/modules/contrib/examples/phpunit_example


PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/contrib/examples/phpunit_example
..................................                                34 / 34 (100%)

Time: 48.62 seconds, Memory: 6.00MB

OK (34 tests, 41 assertions)

Option 4

IDE/text editor integration


fit


[.header: alignment(center)]

Types of tests


[.header: alignment(center)]

1. Arrange

2. Act

3. Assert


[.header: #53B0EB]

Functional tests

  • Tests functionality
  • Interacts with database
  • Full Drupal installation
  • Slower to run
  • With/without JavaScript

^ testing profile


Exercise

Let's write a
functional test


  • Create a web/modules/custom/drupalbristol directory
  • Create a drupalbristol.info.yml file

# drupalbristol.info.yml

name: Drupal Bristol
core: 8.x
type: module

  • Create a tests/src/Functional directory
  • Create an ExampleFunctionalTest.php file

// ExampleFunctionalTest.php

namespace Drupal\Tests\drupalbristol\Functional;

use Drupal\Tests\BrowserTestBase;

class ExampleFunctionalTest extends BrowserTestBase {

}



// ExampleFunctionalTest.php

public function testExamplePage() {
  $this->drupalGet('/example-one');

  $this->assertSession()->statusCodeEquals(200);
}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\drupalbristol\Functional\ExampleFunctionalTest

Behat\Mink\Exception\ExpectationException : Current response status code is 404, but 200 expected.
 /var/www/vendor/behat/mink/src/WebAssert.php:768
 /var/www/vendor/behat/mink/src/WebAssert.php:130
 /var/www/web/modules/custom/drupalbristol/tests/src/Functional/ExampleFunctionalTest.php:14

Time: 18.2 seconds, Memory: 6.00MB

ERRORS!
Tests: 1, Assertions: 2, Errors: 1.

  • Create a drupalbristol.routing.yml file
  • Create a Controller

# drupalbristol.routing.yml

drupalbristol.example:
  path: '/example-one'
  defaults:
    _controller: 'Drupal\drupalbristol\Controllers\ExampleController::index'
  requirements:
    _access: 'TRUE'

// src/Controllers/ExampleController.php

namespace Drupal\drupalbristol\Controllers;

use Drupal\Core\Controller\ControllerBase;

class ExampleController extends ControllerBase {

  public function index() {
    return ['#markup' => $this->t('Drupal Testing Workshop')];
  }

}

class ExampleFunctionalTest extends BrowserTestBase {

  protected static $modules = ['drupalbristol'];

  ...
}

[.header: #53B0EB]

Kernel tests

  • Integration tests
  • Can install modules, interact with services, container, database
  • Minimal Drupal bootstrap
  • Faster than functional tests
  • More setup required

Exercise

Let's write a
kernel test


  • Create a _tests/src/Kernel directory
  • Create an ExampleKernelTest.php file
  • Create a Service
  • Use the service within the test to perform an action

// tests/src/Kernel/ExampleKernelTest.php

namespace Drupal\Tests\drupalbristol\Kernel;

use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\user\Entity\User;

class ExampleKernelTest extends EntityKernelTestBase {

  public static $modules = ['drupalbristol'];

  public function testUserDeleter() {
  }

}


// tests/src/Kernel/ExampleKernelTest.php

public function testUserDeleter {
  $user = $this->createUser();

  $this->assertInstanceOf(User::class, $user);

  /** @var \Drupal\drupalbristol\Services\UserDeleter $user_deleter */
  $user_deleter = \Drupal::service('drupalbristol.user_deleter');
  $user_deleter->delete($user);

  $user = $this->reloadEntity($user);

  $this->assertNull($user);
}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\drupalbristol\Kernel\ExampleKernelTest

Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException : You have requested a non-existent service "drupalbristol.user_deleter".
 /var/www/vendor/symfony/dependency-injection/ContainerBuilder.php:1043
 /var/www/vendor/symfony/dependency-injection/ContainerBuilder.php:610
 /var/www/vendor/symfony/dependency-injection/ContainerBuilder.php:588
 /var/www/web/core/lib/Drupal.php:159
 /var/www/web/modules/custom/drupalbristol/tests/src/Kernel/ExampleKernelTest.php:24

Time: 7.06 seconds, Memory: 6.00MB


ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

Process finished with exit code 2

# drupalbristol.services.yml

services:
  drupalbristol.user_deleter:
    class: 'Drupal\drupalbristol\Services\UserDeleter'
    arguments: []

// src/Services/UserDeleter.php

namespace Drupal\drupalbristol\Services;

use Drupal\Core\Session\AccountInterface;

class UserDeleter {

  public function delete(AccountInterface $user) {
    user_delete($user->id());
  }

}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\drupalbristol\Kernel\ExampleKernelTest

Drupal\Core\Entity\EntityStorageException : SQLSTATE[HY000]: General error: 1 no such table: test89378988.users_data: DELETE FROM {users_data}
WHERE uid IN (:db_condition_placeholder_0); Array
(
    [:db_condition_placeholder_0] => 1
)

 /var/www/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php:777
 /var/www/web/core/includes/entity.inc:281
 /var/www/web/core/modules/user/user.module:878
 /var/www/web/core/modules/user/user.module:865
 /var/www/web/modules/custom/drupalbristol/src/Services/UserDeleter.php:10
 /var/www/web/modules/custom/drupalbristol/tests/src/Kernel/ExampleKernelTest.php:25

 Caused by
 Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[HY000]: General error: 1 no such table: test89378988.users_data: DELETE FROM {users_data}
 WHERE uid IN (:db_condition_placeholder_0); Array
 (
     [:db_condition_placeholder_0] => 1
 )

Time: 6.55 seconds, Memory: 6.00MB

ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

Process finished with exit code 2

// tests/src/Kernel/ExampleKernelTest.php

protected function setUp() {
  parent::setUp();

  $this->installSchema('user', ['users_data']);
}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\drupalbristol\Kernel\ExampleKernelTest


Time: 7.38 seconds, Memory: 6.00MB

OK (1 test, 5 assertions)

Process finished with exit code 0


[.header: #53B0EB]

Unit tests

  • Tests PHP logic
  • No database interaction
  • Fast to run
  • Tightly coupled
  • Mocking dependencies
  • Hard to refactor

Exercise

Let's write a
unit test


// tests/src/Unit/Services/ExampleUnitTest.php

namespace Drupal\Tests\drupalbristol\Unit;

use Drupal\Tests\UnitTestCase;

class ExampleUnitTest extends UnitTestCase {

  public function testAdd() {
    $this->assertEquals(5, (new Calculator(3))->add(2)->calculate());
  }
}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\drupalbristol\Unit\ExampleUnitTest

Error : Class 'Drupal\Tests\drupalbristol\Unit\Calculator' not found
 /var/www/web/modules/custom/drupalbristol/tests/src/Unit/Services/ExampleUnitTest.php:10

Time: 5.13 seconds, Memory: 6.00MB


ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

// src/Services/Calculator.php

namespace Drupal\drupalbristol\Services;

class Calculator {

  private $total;

  public function __construct($value) {
    $this->total = $value;
  }

  public function add($value) {
    $this->total += $value;

    return $this;
  }

  public function calculate() {
    return $this->total;
  }

}

// tests/src/Unit/Services/ExampleUnitTest.php

namespace Drupal\Tests\drupalbristol\Unit;

use Drupal\drupalbristol\Services\Calculator;
use Drupal\Tests\UnitTestCase;

class ExampleUnitTest extends UnitTestCase {

  public function testAdd() {
    $this->assertEquals(5, (new Calculator(3))->add(2)->calculate());
  }

}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\drupalbristol\Unit\ExampleUnitTest


Time: 4.55 seconds, Memory: 4.00MB

OK (1 test, 1 assertion)

[.header: alignment(center)]

Test driven
development (TDD)


Test Driven Development

  • Write a test
  • Test fails
  • Write code
  • Test passes
  • Refactor
  • Repeat

[.background-color: #FFFFFF] [.footer: https://github.com/foundersandcoders/testing-tdd-intro] [.footer-style: #2F2F2F]

100%


How I Write Tests - "Outside In"

  • Start with functional tests
  • Drop down to kernel or unit tests where needed
  • Programming by wishful thinking
  • Write comments first, then fill in the code
  • Sometimes write assertions first

Exercise

Let's build a blog using test driven development


Acceptance criteria

  • As a site visitor
  • I want to see a list of published articles at /blog
  • Ordered by post date

Tasks

  • Ensure the blog page exists
  • Ensure only published articles are shown
  • Ensure the articles are shown in the correct order

Implementation

  • Use views module
  • Do the mininum amount at each step, make no assumptions, let the tests guide us
  • Start with functional test

Step 1

Create the module


# tdd_blog.info.yml

name: 'TDD Blog'
core: '8.x'
type: 'module'

Step 2

Ensure the blog page exists


<?php

// tests/src/Functional/BlogPageTest.php

namespace Drupal\Tests\tdd_blog\Functional;

use Drupal\Tests\BrowserTestBase;

class BlogPageTest extends BrowserTestBase {

  protected static $modules = ['tdd_blog'];

  public function testBlogPageExists() {
    $this->drupalGet('/blog');

    $this->assertSession()->statusCodeEquals(200);
  }

}

<?php

// tests/src/Functional/BlogPageTest.php

namespace Drupal\Tests\tdd_blog\Functional;

use Drupal\Tests\BrowserTestBase;

class BlogPageTest extends BrowserTestBase {

  protected static $modules = ['tdd_blog'];

  public function testBlogPageExists() {
    $this->drupalGet('/blog');

    $this->assertSession()->statusCodeEquals(200);
  }

}

<?php

// tests/src/Functional/BlogPageTest.php

namespace Drupal\Tests\tdd_blog\Functional;

use Drupal\Tests\BrowserTestBase;

class BlogPageTest extends BrowserTestBase {

  protected static $modules = ['tdd_blog'];

  public function testBlogPageExists() {
    $this->drupalGet('/blog');

    $this->assertSession()->statusCodeEquals(200);
  }

}

<?php

// tests/src/Functional/BlogPageTest.php

namespace Drupal\Tests\tdd_blog\Functional;

use Drupal\Tests\BrowserTestBase;

class BlogPageTest extends BrowserTestBase {

  protected static $modules = ['tdd_blog'];

  public function testBlogPageExists() {
    $this->drupalGet('/blog');

    $this->assertSession()->statusCodeEquals(200);
  }

}

<?php

// tests/src/Functional/BlogPageTest.php

namespace Drupal\Tests\tdd_blog\Functional;

use Drupal\Tests\BrowserTestBase;

class BlogPageTest extends BrowserTestBase {

  protected static $modules = ['tdd_blog'];

  public function testBlogPageExists() {
    $this->drupalGet('/blog');

    $this->assertSession()->statusCodeEquals(200);
  }

}

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 19.31 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.

/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13

ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 19.31 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.

/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13

ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 19.31 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.

/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13

ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 19.31 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.

/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13

ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 19.31 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.

/var/www/vendor/behat/mink/src/WebAssert.php:768
/var/www/vendor/behat/mink/src/WebAssert.php:130
/var/www/web/modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php:13

ERRORS!
Tests: 1, Assertions: 3, Errors: 1.

  • The view has not been created
  • Create a new view
  • Set the path
  • Export the config
  • Copy it into the module's config/install directory

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 16.02 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by <em class="placeholder">tdd_blog</em>
have unmet dependencies: <em class="placeholder">views.view.blog (node.type.article, node, views)</em>

/var/www/web/core/lib/Drupal/Core/Config/UnmetDependenciesException.php:98
/var/www/web/core/lib/Drupal/Core/Config/ConfigInstaller.php:469
/var/www/web/core/lib/Drupal/Core/ProxyClass/Config/ConfigInstaller.php:132
/var/www/web/core/lib/Drupal/Core/Extension/ModuleInstaller.php:145
/var/www/web/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php:83
/var/www/web/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:437
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:1055
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:490

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

# tdd_blog.info.yml

name: 'TDD Dublin'
description: 'A demo module to show test driven module development.'
core: 8.x
type: module

dependencies:
  - 'drupal:node'
  - 'drupal:views'

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
E                                                                   1 / 1 (100%)

Time: 20 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Functional\BlogPageTest::testBlogPageExists
Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by <em class="placeholder">tdd_blog</em>
have unmet dependencies: <em class="placeholder">views.view.blog (node.type.article)</em>

/var/www/web/core/lib/Drupal/Core/Config/UnmetDependenciesException.php:98
/var/www/web/core/lib/Drupal/Core/Config/ConfigInstaller.php:469
/var/www/web/core/lib/Drupal/Core/ProxyClass/Config/ConfigInstaller.php:132
/var/www/web/core/lib/Drupal/Core/Extension/ModuleInstaller.php:145
/var/www/web/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php:83
/var/www/web/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:437
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:1055
/var/www/web/core/tests/Drupal/Tests/BrowserTestBase.php:490

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

  • Add the article content type

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog
.                                                                   1 / 1 (100%)

Time: 23.36 seconds, Memory: 6.00MB

OK (1 test, 3 assertions)

[.build-lists: false]

Tasks

  • Ensure the blog page exists
  • Ensure only published articles are shown
  • Ensure the articles are shown in the correct order

Step 3

Ensure only published articles are shown


public function testOnlyPublishedArticlesAreShown() {
  // Given I have a mixture of published and unpublished articles,
  // as well as other types of content.

  // When I view the blog page.

  // I should only see the published articles.
}

Option 1

Functional tests


// modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php

public function testOnlyPublishedArticlesAreShown() {
  // Given I have a mixture of published and unpublished articles,
  // as well as other types of content.
  $node1 = $this->drupalCreateNode(['type' => 'page', 'status' => 1]);
  $node2 = $this->drupalCreateNode(['type' => 'article', 'status' => 1]);
  $node3 = $this->drupalCreateNode(['type' => 'article', 'status' => 0]);

  // When I view the blog page.
  $this->drupalGet('/blog');

  // I should only see the published articles.
  $assert = $this->assertSession();
  $assert->pageTextContains($node2->label());
  $assert->pageTextNotContains($node1->label());
  $assert->pageTextNotContains($node3->label());
}

^ Different ways to achieve this. This is taking the functional test approach.


// modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php

public function testOnlyPublishedArticlesAreShown() {
  // Given I have a mixture of published and unpublished articles,
  // as well as other types of content.
  $node1 = $this->drupalCreateNode(['type' => 'page', 'status' => 1]);
  $node2 = $this->drupalCreateNode(['type' => 'article', 'status' => 1]);
  $node3 = $this->drupalCreateNode(['type' => 'article', 'status' => 0]);

  // When I view the blog page.
  $this->drupalGet('/blog');

  // I should only see the published articles.
  $assert = $this->assertSession();
  $assert->pageTextContains($node2->label());
  $assert->pageTextNotContains($node1->label());
  $assert->pageTextNotContains($node3->label());
}

// modules/custom/tdd_blog/tests/src/Functional/BlogPageTest.php

public function testOnlyPublishedArticlesAreShown() {
  // Given I have a mixture of published and unpublished articles,
  // as well as other types of content.
  $node1 = $this->drupalCreateNode(['type' => 'page', 'status' => 1]);
  $node2 = $this->drupalCreateNode(['type' => 'article', 'status' => 1]);
  $node3 = $this->drupalCreateNode(['type' => 'article', 'status' => 0]);

  // When I view the blog page.
  $this->drupalGet('/blog');

  // I should only see the published articles.
  $assert = $this->assertSession();
  $assert->pageTextContains($node2->label());
  $assert->pageTextNotContains($node1->label());
  $assert->pageTextNotContains($node3->label());
}

Option 2

Kernel tests


<?php

namespace Drupal\Tests\tdd_blog\Kernel;

use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;

class BlogPageTest extends EntityKernelTestBase {

  use NodeCreationTrait;

  public static $modules = ['node'];

  public function testOnlyPublishedArticlesAreShown() {
    $this->createNode(['type' => 'page', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 0]);
  }

}

^ Kernel test approach Dropping down a level No need for the brower, not asserting against HTML Faster to run


<?php

namespace Drupal\Tests\tdd_blog\Kernel;

use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;

class BlogPageTest extends EntityKernelTestBase {

  use NodeCreationTrait;

  public static $modules = ['node'];

  public function testOnlyPublishedArticlesAreShown() {
    $this->createNode(['type' => 'page', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 0]);
  }

}

<?php

namespace Drupal\Tests\tdd_blog\Kernel;

use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;

class BlogPageTest extends EntityKernelTestBase {

  use NodeCreationTrait;

  public static $modules = ['node'];

  public function testOnlyPublishedArticlesAreShown() {
    $this->createNode(['type' => 'page', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 0]);
  }

}

<?php

namespace Drupal\Tests\tdd_blog\Kernel;

use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;

class BlogPageTest extends EntityKernelTestBase {

  use NodeCreationTrait;

  public static $modules = ['node'];

  public function testOnlyPublishedArticlesAreShown() {
    $this->createNode(['type' => 'page', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 1]);
    $this->createNode(['type' => 'article', 'status' => 0]);
  }

}

docker@cli:/var/www/web$ ../vendor/bin/phpunit -c core modules/custom/tdd_blog/tests/src/Kernel/
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing modules/custom/tdd_blog/tests/src/Kernel/
E                                                                   1 / 1 (100%)

Time: 6.22 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testOnlyPublishedArticlesAreShown
Error: Call to a member function id() on boolean

/var/www/web/core/modules/filter/filter.module:212
/var/www/web/core/modules/node/tests/src/Traits/NodeCreationTrait.php:73
/var/www/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:13

ERRORS!
Tests: 1, Assertions: 2, Errors: 1.

public function testOnlyPublishedArticlesAreShown() {
  $this->installConfig(['filter']);

  $this->createNode(['type' => 'page', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 0]);
}

public function testOnlyPublishedArticlesAreShown() {
  $this->installConfig(['filter']);

  $this->createNode(['type' => 'page', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 0]);

  $results = views_get_view_result('blog');
}

...

public static $modules = ['node', 'tdd_blog', 'views'];

public function testOnlyPublishedArticlesAreShown() {
  $this->installConfig(['filter', 'tdd_blog']);

  $this->createNode(['type' => 'page', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 0]);

  $results = views_get_view_result('blog');

  $this->assertCount(1, $results);
  $this->assertEquals(2, $results[0]->_entity->id());
}

...

public static $modules = ['node', 'tdd_blog', 'views'];

public function testOnlyPublishedArticlesAreShown() {
  $this->installConfig(['filter', 'tdd_blog']);

  $this->createNode(['type' => 'page', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 0]);

  $results = views_get_view_result('blog');

  $this->assertCount(1, $results);
  $this->assertEquals(2, $results[0]->_entity->id());
}

...

public static $modules = ['node', 'tdd_blog', 'views'];

public function testOnlyPublishedArticlesAreShown() {
  $this->installConfig(['filter', 'tdd_blog']);

  $this->createNode(['type' => 'page', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 1]);
  $this->createNode(['type' => 'article', 'status' => 0]);

  $results = views_get_view_result('blog');

  $this->assertCount(1, $results);
  $this->assertEquals(2, $results[0]->_entity->id());
}

^ Assert Should only be one result, should be node 2 Node IDs are reset on each test method


PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/custom/tdd_blog/tests/src/Kernel
F                                                                   1 / 1 (100%)

Time: 2.16 seconds, Memory: 6.00MB

There was 1 failure:

1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testOnlyPublishedArticlesAreShown
Failed asserting that actual size 3 matches expected size 1.

/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:23

FAILURES!
Tests: 1, Assertions: 4, Failures: 1.

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/custom/tdd_blog/tests/src/Kernel
F                                                                   1 / 1 (100%)

Time: 2.16 seconds, Memory: 6.00MB

There was 1 failure:

1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testOnlyPublishedArticlesAreShown
Failed asserting that actual size 3 matches expected size 1.

/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:23

FAILURES!
Tests: 1, Assertions: 4, Failures: 1.

  • There are no filters on the view
  • Add the filters
  • Export and save the view

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/custom/tdd_blog/tests/src/Kernel
.                                                                   1 / 1 (100%)

Time: 2.02 seconds, Memory: 6.00MB

OK (1 test, 6 assertions)

[.build-lists: false]

Tasks

  • Ensure the blog page exists
  • Ensure only published articles are shown
  • Ensure the articles are shown in the correct order

Step 4

Ensure the articles are ordered by date


// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php

public function testArticlesAreOrderedByDate() {
  // Given that I have numerous articles with different post dates.

  // When I go to the blog page.

  // The articles are ordered by post date.
}

// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php

public function testArticlesAreOrderedByDate() {
  // Given that I have numerous articles with different post dates.
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);

  // When I go to the blog page.

  // The articles are ordered by post date.
}

$this->createNode([
  'type' => 'article',
  'created' => (new \DateTime())->modify('+1 day')->getTimestamp(),
]);

^ Array of default values


// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php

public function testArticlesAreOrderedByDate() {
  // Given that I have numerous articles with different post dates.
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);

  // When I go to the blog page.
  $results = views_get_view_result('blog');

  // The articles are ordered by post date.
}

// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php

public function testArticlesAreOrderedByDate() {
  // Given that I have numerous articles with different post dates.
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);

  // When I go to the blog page.
  $results = views_get_view_result('blog');

  $nids = array_map(function(ResultRow $result) {
    return $result->_entity->id();
  }, $results);

  // The articles are ordered by post date.
}

// modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php

public function testArticlesAreOrderedByDate() {
  // Given that I have numerous articles with different post dates.
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 day')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 month')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+3 days')->getTimestamp()]);
  $this->createNode(['type' => 'article', 'created' => (new \DateTime())->modify('+1 hour')->getTimestamp()]);

  // When I go to the blog page.
  $results = views_get_view_result('blog');

  $nids = array_map(function(ResultRow $result) {
    return $result->_entity->id();
  }, $results);

  // The articles are ordered by post date.
  $this->assertEquals([4, 1, 3, 2], $nids);
}

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/custom/tdd_blog/tests/src/Kernel
F                                                                   1 / 1 (100%)

Time: 1.42 seconds, Memory: 6.00MB

There was 1 failure:

1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testArticlesAreOrderedByDate
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    0 => 4
-    1 => 1
-    2 => 3
-    3 => 2
+    0 => '3'
+    1 => '2'
+    2 => '4'
+    3 => '1'

/Users/opdavies/Code/drupal-testing-workshop/web/core/tests/Drupal/KernelTests/KernelTestBase.php:1114
/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:43

FAILURES!
Tests: 1, Assertions: 4, Failures: 1.

[.text: comic sans]

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/custom/tdd_blog/tests/src/Kernel
F                                                                   1 / 1 (100%)

Time: 1.42 seconds, Memory: 6.00MB

There was 1 failure:

1) Drupal\Tests\tdd_blog\Kernel\BlogPageTest::testArticlesAreOrderedByDate
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    0 => 4
-    1 => 1
-    2 => 3
-    3 => 2
+    0 => '3'
+    1 => '2'
+    2 => '4'
+    3 => '1'

/Users/opdavies/Code/drupal-testing-workshop/web/core/tests/Drupal/KernelTests/KernelTestBase.php:1114
/Users/opdavies/Code/drupal-testing-workshop/web/modules/custom/tdd_blog/tests/src/Kernel/BlogPageTest.php:43

FAILURES!
Tests: 1, Assertions: 4, Failures: 1.

  • There is no sort order defined on the view
  • Add the sort order
  • Re-export the view

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

Testing web/modules/custom/tdd_blog/tests/src/Kernel
.                                                                   1 / 1 (100%)

Time: 1.74 seconds, Memory: 6.00MB

OK (1 test, 5 assertions)

[.build-lists: false]

Tasks

  • Ensure the blog page exists
  • Ensure only published articles are shown
  • Ensure the articles are shown in the correct order

[.header: alignment(center)]

Questions?


[.header: alignment(center)]

Thanks

@opdavies

oliverdavies.uk