{ "uuid": [ { "value": "4c1497cc-4638-40fc-b045-f342c4d3e5b3" } ], "langcode": [ { "value": "en" } ], "type": [ { "target_id": "daily_email", "target_type": "node_type", "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7" } ], "revision_timestamp": [ { "value": "2025-05-11T09:00:24+00:00" } ], "revision_uid": [ { "target_type": "user", "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" } ], "revision_log": [], "status": [ { "value": true } ], "uid": [ { "target_type": "user", "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" } ], "title": [ { "value": "A sneak peek of my Drupal automated testing course\n" } ], "created": [ { "value": "2023-12-25T00:00:00+00:00" } ], "changed": [ { "value": "2025-05-11T09:00:24+00:00" } ], "promote": [ { "value": false } ], "sticky": [ { "value": false } ], "default_langcode": [ { "value": true } ], "revision_translation_affected": [ { "value": true } ], "path": [ { "alias": "\/daily\/2023\/12\/25\/zero-to-test", "langcode": "en" } ], "body": [ { "value": "\n
Happy Christmas!<\/p>\n\n
Here's my present - a sneak peek at the first lesson in my free upcoming Automated Testing for Drupal email course.<\/p>\n\n
In this lesson, we start from scratch and end with a working test suite.<\/p>\n\n
If you like it, register for free and get the full course<\/a> when it launches.<\/p>\n\n If you don't have one, you'll need to create a new Drupal project. I'd suggest using Drupal 10.2 and the instructions at https:\/\/www.drupal.org\/download<\/a>.<\/p>\n\n You'll need PHP<\/a> and Composer<\/a>.<\/p>\n\n First, run At this point, you should have a Finally, run You don't need to install Drupal - as long as you see the installation page, that's fine.<\/p>\n\n Before adding tests, you must create a module to place them in.<\/p>\n\n Run This is the minimum content needed for a module to be installable.<\/p>\n\n Test classes are placed within each module's Run Then, add this content.<\/p>\n\n Note: within a test class, the namespace is With the boilerplate class added, create a test method within it:<\/p>\n\n Note: the class name must be suffixed with Now we have a test with an assertion, we need to run it and see if it passes.<\/p>\n\n On the command line, run PHPUnit\\TextUI\\RuntimeException: Class \"Drupal\\Tests\\BrowserTestBase\" not found.<\/p>\n<\/blockquote>\n\n This isn't an assertion failure, but that PHPUnit can't find the files it needs to run.<\/p>\n\n To fix this, let's configure PHPUnit.<\/p>\n\n Create a new This is based on Namely, setting the PHPUnit now knows where the files are, to connect to Drupal at http:\/\/localhost:8000<\/a> (matching the PHP web server address) and an SQLite database.<\/p>\n\n I've also added a Running the tests again will give the expected error about a failing assertion:<\/p>\n\n Failed asserting that false is true.<\/p>\n<\/blockquote>\n\n Fix the assertion in the test by changing OK (1 test, 2 assertions)<\/p>\n<\/blockquote>\n\n Now you have as passing test and know PHPUnit is working, let's improve it.<\/p>\n\n Instead of the basic check, let's check whether certain pages exist and are accessible.<\/p>\n\n To keep things simple and focused on writing and running tests, let's use some standard Drupal pages - the front and administration pages instead of writing your own.<\/p>\n\n As you're writing functional tests by extending Replace the These tests will make HTTP requests to the specified paths and assert the status code on the response matches the expected values.<\/p>\n\n I'm using the constants on the Running 1) Drupal\\Tests\\atdc\\Functional\\ExampleTest::testFrontPage\n Behat\\Mink\\Exception\\ExpectationException: Current response status code is 200, but 403 expected.<\/p>\n \n 2) Drupal\\Tests\\atdc\\Functional\\ExampleTest::testAdminPage\n Behat\\Mink\\Exception\\ExpectationException: Current response status code is 403, but 200 expected.<\/p>\n \n ERRORS! The responses are not returning the expected status codes, so the tests are failing.<\/p>\n\n Reviewing them, the front page should return a 200 response code ( As we're logged out, the administration page should return a 403 ( Swapping the assertions should get the tests to pass.<\/p>\n\n Now, running OK (2 tests, 4 assertions)<\/p>\n<\/blockquote>\n\n Congratulations!<\/p>\n\n In this lesson, you've created a new Drupal 10 project, configured PHPUnit and created a custom module with your first passing browser tests.<\/p>\n\n From this, you can hopefully see that automated testing doesn't need to be difficult, and the configuration you've done here will work for the upcoming lessons, where you'll expand on what you've done and explore more that Drupal and PHPUnit have to offer.<\/p>\n\n I hope you enjoyed this sneak peek, and if you'd like to receive the course once it's complete, register here for free<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n Happy Christmas!<\/p>\n\n Here's my present - a sneak peek at the first lesson in my free upcoming Automated Testing for Drupal email course.<\/p>\n\n In this lesson, we start from scratch and end with a working test suite.<\/p>\n\n If you like it, register for free and get the full course<\/a> when it launches.<\/p>\n\n If you don't have one, you'll need to create a new Drupal project. I'd suggest using Drupal 10.2 and the instructions at https:\/\/www.drupal.org\/download<\/a>.<\/p>\n\n You'll need PHP<\/a> and Composer<\/a>.<\/p>\n\n First, run At this point, you should have a Finally, run You don't need to install Drupal - as long as you see the installation page, that's fine.<\/p>\n\n Before adding tests, you must create a module to place them in.<\/p>\n\n Run This is the minimum content needed for a module to be installable.<\/p>\n\n Test classes are placed within each module's Run Then, add this content.<\/p>\n\n Note: within a test class, the namespace is With the boilerplate class added, create a test method within it:<\/p>\n\n Note: the class name must be suffixed with Now we have a test with an assertion, we need to run it and see if it passes.<\/p>\n\n On the command line, run PHPUnit\\TextUI\\RuntimeException: Class \"Drupal\\Tests\\BrowserTestBase\" not found.<\/p>\n<\/blockquote>\n\n This isn't an assertion failure, but that PHPUnit can't find the files it needs to run.<\/p>\n\n To fix this, let's configure PHPUnit.<\/p>\n\n Create a new This is based on Namely, setting the PHPUnit now knows where the files are, to connect to Drupal at http:\/\/localhost:8000<\/a> (matching the PHP web server address) and an SQLite database.<\/p>\n\n I've also added a Running the tests again will give the expected error about a failing assertion:<\/p>\n\n Failed asserting that false is true.<\/p>\n<\/blockquote>\n\n Fix the assertion in the test by changing OK (1 test, 2 assertions)<\/p>\n<\/blockquote>\n\n Now you have as passing test and know PHPUnit is working, let's improve it.<\/p>\n\n Instead of the basic check, let's check whether certain pages exist and are accessible.<\/p>\n\n To keep things simple and focused on writing and running tests, let's use some standard Drupal pages - the front and administration pages instead of writing your own.<\/p>\n\n As you're writing functional tests by extending Replace the These tests will make HTTP requests to the specified paths and assert the status code on the response matches the expected values.<\/p>\n\n I'm using the constants on the Running 1) Drupal\\Tests\\atdc\\Functional\\ExampleTest::testFrontPage\n Behat\\Mink\\Exception\\ExpectationException: Current response status code is 200, but 403 expected.<\/p>\n \n 2) Drupal\\Tests\\atdc\\Functional\\ExampleTest::testAdminPage\n Behat\\Mink\\Exception\\ExpectationException: Current response status code is 403, but 200 expected.<\/p>\n \n ERRORS! The responses are not returning the expected status codes, so the tests are failing.<\/p>\n\n Reviewing them, the front page should return a 200 response code ( As we're logged out, the administration page should return a 403 ( Swapping the assertions should get the tests to pass.<\/p>\n\n Now, running OK (2 tests, 4 assertions)<\/p>\n<\/blockquote>\n\n Congratulations!<\/p>\n\n In this lesson, you've created a new Drupal 10 project, configured PHPUnit and created a custom module with your first passing browser tests.<\/p>\n\n From this, you can hopefully see that automated testing doesn't need to be difficult, and the configuration you've done here will work for the upcoming lessons, where you'll expand on what you've done and explore more that Drupal and PHPUnit have to offer.<\/p>\n\n I hope you enjoyed this sneak peek, and if you'd like to receive the course once it's complete, register here for free<\/a>.<\/p>\n\n ",
"summary": null
}
]
}Creating a Drupal project<\/h2>\n\n
composer create-project drupal\/recommended-project drupal<\/code> followed by
cd drupal && composer require --dev drupal\/core-dev<\/code> to add the development dependencies, including PHPUnit.<\/p>\n\n
web<\/code> directory and a
phpunit<\/code> file within
vendor\/bin<\/code>.<\/p>\n\n
php -S 0.0.0.0:8000 -t web<\/code> to start a local web server.<\/p>\n\n
Creating a custom module<\/h2>\n\n
mkdir -p web\/modules\/custom\/atdc<\/code> to create an empty module directory, and create an
atdc.info.yml<\/code> file within it with this content:<\/p>\n\n
name: Example\ntype: module\ncore_version_requirement: ^10\npackage: Custom\n<\/code><\/pre>\n\n
Writing your first test class<\/h3>\n\n
tests\/src<\/code> directory.<\/p>\n\n
mkdir -p web\/modules\/custom\/atdc\/tests\/src\/Functional && touch web\/modules\/custom\/atdc\/tests\/src\/Functional\/ExampleTest.php<\/code> to create the directory structure and a blank test class.<\/p>\n\n
<?php\n\nnamespace Drupal\\Tests\\atdc\\Functional;\n\nuse Drupal\\Tests\\BrowserTestBase;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass ExampleTest extends BrowserTestBase {\n\n\u00a0 public $defaultTheme = 'stark';\n\n}\n<\/code><\/pre>\n\n
Drupal\\Tests\\{module_name}<\/code> instead of
Drupal\\{module_name}<\/code>.<\/p>\n\n
public function testBasic(): void {\n\u00a0 self::assertTrue(FALSE);\n}\n<\/code><\/pre>\n\n
Test<\/code>, and the test method must be prefixed with
test<\/code> for them to be run.<\/p>\n\n
Running the test<\/h2>\n\n
vendor\/bin\/phpunit web\/modules\/custom<\/code>, and you'll get an error like:<\/p>\n\n
\n
Configuring PHPUnit<\/h2>\n\n
phpunit.xml.dist<\/code> file at the root of your project, with this content:<\/p>\n\n
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"web\/core\/tests\/bootstrap.php\" colors=\"true\">\n <php>\n <env name=\"SIMPLETEST_BASE_URL\" value=\"http:\/\/localhost:8000\"\/>\n <env name=\"SIMPLETEST_DB\" value=\"sqlite:\/\/localhost\/\/dev\/shm\/test.sqlite\"\/>\n <ini name=\"error_reporting\" value=\"32767\"\/>\n <ini name=\"memory_limit\" value=\"-1\"\/>\n <\/php>\n <testsuites>\n <testsuite name=\"Example tests\">\n <directory suffix=\"Test.php\">.\/web\/modules\/**<\/directory>\n <\/testsuite>\n <\/testsuites>\n<\/phpunit>\n<\/code><\/pre>\n\n
web\/core\/phpunit.xml.dist<\/code> with some project-specific changes.<\/p>\n\n
bootstrap<\/code> value to include the
web\/core<\/code> path and fix the error, and populating the
SIMPLETEST_BASE_URL<\/code> and
SIMPLETEST_DB<\/code> environment variables.<\/p>\n\n
testsuite<\/code> that declares where any test classes will be located so the path doesn't need to be specified on the command line.<\/p>\n\n
Re-running the tests<\/h2>\n\n
\n
FALSE<\/code> to
TRUE<\/code>, run
vendor\/bin\/phpunit<\/code> again, and you should see a passing test.<\/p>\n\n
\n
Improving the tests<\/h2>\n\n
BrowserTestBase<\/code>, you can make HTTP requests to the web server, and make assertions on the responses.<\/p>\n\n
testBasic<\/code> test method with the following:<\/p>\n\n
public function testFrontPage(): void {\n $this->drupalGet('\/');\n\n $assert = $this->assertSession();\n $assert->statusCodeEquals(Response::HTTP_FORBIDDEN);\n}\n\npublic function testAdminPage(): void {\n $this->drupalGet('\/admin');\n\n $assert = $this->assertSession();\n $assert->statusCodeEquals(Response::HTTP_OK);\n}\n<\/code><\/pre>\n\n
Response<\/code> class, but you can also use the status code numbers - e.g.
200<\/code> and
403<\/code>.<\/p>\n\n
Running the updated tests<\/h2>\n\n
vendor\/bin\/phpunit<\/code>, you'll get two errors:<\/p>\n\n
\n
\n Tests: 2, Assertions: 4, Errors: 2.<\/p>\n<\/blockquote>\n\nHTTP_OK<\/code>) as it's accessible to all users, including anonymous users.<\/p>\n\n
HTTP_FORBIDDEN<\/code>).<\/p>\n\n
vendor\/bin\/phpunit<\/code> returns no errors or failures.<\/p>\n\n
\n
Conclusion<\/h2>\n\n
Creating a Drupal project<\/h2>\n\n
composer create-project drupal\/recommended-project drupal<\/code> followed by
cd drupal && composer require --dev drupal\/core-dev<\/code> to add the development dependencies, including PHPUnit.<\/p>\n\n
web<\/code> directory and a
phpunit<\/code> file within
vendor\/bin<\/code>.<\/p>\n\n
php -S 0.0.0.0:8000 -t web<\/code> to start a local web server.<\/p>\n\n
Creating a custom module<\/h2>\n\n
mkdir -p web\/modules\/custom\/atdc<\/code> to create an empty module directory, and create an
atdc.info.yml<\/code> file within it with this content:<\/p>\n\n
name: Example\ntype: module\ncore_version_requirement: ^10\npackage: Custom\n<\/code><\/pre>\n\n
Writing your first test class<\/h3>\n\n
tests\/src<\/code> directory.<\/p>\n\n
mkdir -p web\/modules\/custom\/atdc\/tests\/src\/Functional && touch web\/modules\/custom\/atdc\/tests\/src\/Functional\/ExampleTest.php<\/code> to create the directory structure and a blank test class.<\/p>\n\n
<?php\n\nnamespace Drupal\\Tests\\atdc\\Functional;\n\nuse Drupal\\Tests\\BrowserTestBase;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass ExampleTest extends BrowserTestBase {\n\n public $defaultTheme = 'stark';\n\n}\n<\/code><\/pre>\n\n
Drupal\\Tests\\{module_name}<\/code> instead of
Drupal\\{module_name}<\/code>.<\/p>\n\n
public function testBasic(): void {\n self::assertTrue(FALSE);\n}\n<\/code><\/pre>\n\n
Test<\/code>, and the test method must be prefixed with
test<\/code> for them to be run.<\/p>\n\n
Running the test<\/h2>\n\n
vendor\/bin\/phpunit web\/modules\/custom<\/code>, and you'll get an error like:<\/p>\n\n
\n
Configuring PHPUnit<\/h2>\n\n
phpunit.xml.dist<\/code> file at the root of your project, with this content:<\/p>\n\n
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"web\/core\/tests\/bootstrap.php\" colors=\"true\">\n <php>\n <env name=\"SIMPLETEST_BASE_URL\" value=\"http:\/\/localhost:8000\"\/>\n <env name=\"SIMPLETEST_DB\" value=\"sqlite:\/\/localhost\/\/dev\/shm\/test.sqlite\"\/>\n <ini name=\"error_reporting\" value=\"32767\"\/>\n <ini name=\"memory_limit\" value=\"-1\"\/>\n <\/php>\n <testsuites>\n <testsuite name=\"Example tests\">\n <directory suffix=\"Test.php\">.\/web\/modules\/**<\/directory>\n <\/testsuite>\n <\/testsuites>\n<\/phpunit>\n<\/code><\/pre>\n\n
web\/core\/phpunit.xml.dist<\/code> with some project-specific changes.<\/p>\n\n
bootstrap<\/code> value to include the
web\/core<\/code> path and fix the error, and populating the
SIMPLETEST_BASE_URL<\/code> and
SIMPLETEST_DB<\/code> environment variables.<\/p>\n\n
testsuite<\/code> that declares where any test classes will be located so the path doesn't need to be specified on the command line.<\/p>\n\n
Re-running the tests<\/h2>\n\n
\n
FALSE<\/code> to
TRUE<\/code>, run
vendor\/bin\/phpunit<\/code> again, and you should see a passing test.<\/p>\n\n
\n
Improving the tests<\/h2>\n\n
BrowserTestBase<\/code>, you can make HTTP requests to the web server, and make assertions on the responses.<\/p>\n\n
testBasic<\/code> test method with the following:<\/p>\n\n
public function testFrontPage(): void {\n $this->drupalGet('\/');\n\n $assert = $this->assertSession();\n $assert->statusCodeEquals(Response::HTTP_FORBIDDEN);\n}\n\npublic function testAdminPage(): void {\n $this->drupalGet('\/admin');\n\n $assert = $this->assertSession();\n $assert->statusCodeEquals(Response::HTTP_OK);\n}\n<\/code><\/pre>\n\n
Response<\/code> class, but you can also use the status code numbers - e.g.
200<\/code> and
403<\/code>.<\/p>\n\n
Running the updated tests<\/h2>\n\n
vendor\/bin\/phpunit<\/code>, you'll get two errors:<\/p>\n\n
\n
\n Tests: 2, Assertions: 4, Errors: 2.<\/p>\n<\/blockquote>\n\nHTTP_OK<\/code>) as it's accessible to all users, including anonymous users.<\/p>\n\n
HTTP_FORBIDDEN<\/code>).<\/p>\n\n
vendor\/bin\/phpunit<\/code> returns no errors or failures.<\/p>\n\n
\n
Conclusion<\/h2>\n\n