--- title: > A sneak peek of my Drupal automated testing course pubDate: 2023-12-25 permalink: >- daily/2023/12/25/zero-to-test tags: - software-development - drupal - php - phpunit - automated-testing - test-driven-development --- Happy Christmas! Here's my present - a sneak peek at the first lesson in my free upcoming Automated Testing for Drupal email course. In this lesson, we start from scratch and end with a working test suite. If you like it, [register for free and get the full course][atdc] when it launches. ## Creating a Drupal project 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 . You'll need [PHP](https://www.php.net/manual/en/install.php) and [Composer](https://getcomposer.org/doc/00-intro.md). First, run `composer create-project drupal/recommended-project drupal` followed by `cd drupal && composer require --dev drupal/core-dev` to add the development dependencies, including PHPUnit. At this point, you should have a `web` directory and a `phpunit` file within `vendor/bin`. Finally, run `php -S 0.0.0.0:8000 -t web` to start a local web server. You don't need to install Drupal - as long as you see the installation page, that's fine. ## Creating a custom module Before adding tests, you must create a module to place them in. Run `mkdir -p web/modules/custom/atdc` to create an empty module directory, and create an `atdc.info.yml` file within it with this content: ```language-yaml name: Example type: module core_version_requirement: ^10 package: Custom ``` This is the minimum content needed for a module to be installable. ### Writing your first test class Test classes are placed within each module's `tests/src` directory. Run `mkdir -p web/modules/custom/atdc/tests/src/Functional && touch web/modules/custom/atdc/tests/src/Functional/ExampleTest.php` to create the directory structure and a blank test class. Then, add this content. ```language-php PHPUnit\TextUI\RuntimeException: Class "Drupal\Tests\BrowserTestBase" not found. This isn't an assertion failure, but that PHPUnit can't find the files it needs to run. To fix this, let's configure PHPUnit. ## Configuring PHPUnit Create a new `phpunit.xml.dist` file at the root of your project, with this content: ```xml ./web/modules/** ``` This is based on `web/core/phpunit.xml.dist` with some project-specific changes. Namely, setting the `bootstrap` value to include the `web/core` path and fix the error, and populating the `SIMPLETEST_BASE_URL` and `SIMPLETEST_DB` environment variables. PHPUnit now knows where the files are, to connect to Drupal at (matching the PHP web server address) and an SQLite database. I've also added a `testsuite` that declares where any test classes will be located so the path doesn't need to be specified on the command line. ## Re-running the tests Running the tests again will give the expected error about a failing assertion: > Failed asserting that false is true. Fix the assertion in the test by changing `FALSE` to `TRUE`, run `vendor/bin/phpunit` again, and you should see a passing test. > OK (1 test, 2 assertions) ## Improving the tests Now you have as passing test and know PHPUnit is working, let's improve it. Instead of the basic check, let's check whether certain pages exist and are accessible. 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. As you're writing functional tests by extending `BrowserTestBase`, you can make HTTP requests to the web server, and make assertions on the responses. Replace the `testBasic` test method with the following: ```language-php public function testFrontPage(): void { $this->drupalGet('/'); $assert = $this->assertSession(); $assert->statusCodeEquals(Response::HTTP_FORBIDDEN); } public function testAdminPage(): void { $this->drupalGet('/admin'); $assert = $this->assertSession(); $assert->statusCodeEquals(Response::HTTP_OK); } ``` These tests will make HTTP requests to the specified paths and assert the status code on the response matches the expected values. I'm using the constants on the `Response` class, but you can also use the status code numbers - e.g. `200` and `403`. ## Running the updated tests Running `vendor/bin/phpunit`, you'll get two errors: > 1) Drupal\Tests\atdc\Functional\ExampleTest::testFrontPage > Behat\Mink\Exception\ExpectationException: Current response status code is 200, but 403 expected. > > 2) Drupal\Tests\atdc\Functional\ExampleTest::testAdminPage > Behat\Mink\Exception\ExpectationException: Current response status code is 403, but 200 expected. > > ERRORS!
> Tests: 2, Assertions: 4, Errors: 2. The responses are not returning the expected status codes, so the tests are failing. Reviewing them, the front page should return a 200 response code (`HTTP_OK`) as it's accessible to all users, including anonymous users. As we're logged out, the administration page should return a 403 (`HTTP_FORBIDDEN`). Swapping the assertions should get the tests to pass. Now, running `vendor/bin/phpunit` returns no errors or failures. > OK (2 tests, 4 assertions) Congratulations! ## Conclusion In this lesson, you've created a new Drupal 10 project, configured PHPUnit and created a custom module with your first passing browser tests. 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. I hope you enjoyed this sneak peek, and if you'd like to receive the course once it's complete, [register here for free][atdc]. [atdc]: {{site.url}}/atdc