Re-add syntax highlighting to daily emails and
...ATDC lessons
This commit is contained in:
parent
0d9bb37503
commit
5fbf48d9ac
|
@ -64,7 +64,7 @@ You can also use the `COMPOSE_PROJECT_NAME` variable inside Docker Compose files
|
|||
|
||||
For example, if you use Traefik and needed to override the host URL for a service, the string will be interpolated and the project name would be injected as you'd expect.
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
labels:
|
||||
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(
|
||||
`${COMPOSE_PROJECT_NAME}.docker.localhost`,
|
||||
|
|
|
@ -15,7 +15,7 @@ A Makefile contains a number of named targets that you can reference, and each h
|
|||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
```language-language-yaml
|
||||
# Start the project.
|
||||
start:
|
||||
docker-compose up -d
|
||||
|
|
|
@ -15,7 +15,7 @@ By using one of these tools, you can programatically provision a new, blank serv
|
|||
|
||||
For example, to [create a DigitalOcean droplet](https://docs.ansible.com/ansible/latest/collections/community/digitalocean/digital_ocean_module.htm):
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
---
|
||||
- community.digitalocean.digital_ocean_droplet:
|
||||
image: ubuntu-20-04-x64
|
||||
|
@ -35,7 +35,7 @@ If you needed to create a separate database server or another server for a new e
|
|||
|
||||
[Creating an Amazon EC2 instance](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#ansible-collections-amazon-aws-ec2-instance-module) looks very similar:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
---
|
||||
- amazon.aws.ec2_instance:
|
||||
image_id: ami-123456
|
||||
|
|
|
@ -10,7 +10,7 @@ I cloned a fresh version of my [Docker Examples repository](https://github.com/o
|
|||
|
||||
I ran `mkdir -p web/modules/custom/example/tests/src/Functional` to create the directory structure that I needed, and then `touch web/modules/custom/example/tests/src/Functional/ExampleTest.php` to create a new test file and populated it with some initial code:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\example\Functional;
|
||||
|
@ -27,7 +27,7 @@ class ExampleTest extends BrowserTestBase {
|
|||
|
||||
For the simplest test, I decided to test some existing Drupal core functionality - that an anonymous user can view the front page:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function the_front_page_loads_for_anonymous_users() {
|
||||
$this->drupalGet('<front>');
|
||||
|
@ -42,7 +42,7 @@ As this is existing functionalty, the test passes. I can change either the path
|
|||
|
||||
With the first test working, it's easy to add more for other functionality, such as whether different users should be able to access administration pages:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function the_admin_page_is_not_accessible_to_anonymous_users() {
|
||||
$this->drupalGet('admin');
|
||||
|
|
|
@ -15,7 +15,7 @@ I find that these higher-level types of tests are easier and quicker to set up c
|
|||
|
||||
For example, this `Device` class which is a data transfer object around Drupal's `NodeInterface`. It ensures that the correct type of node is provided, and includes a named constructor and a helper method to retrieve a device's asset ID from a field:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
final class Device {
|
||||
|
||||
private NodeInterface $node;
|
||||
|
@ -47,7 +47,7 @@ I need to specify what value is returned from the `bundle()` method as well as g
|
|||
|
||||
I need to mock the `get()` method and specify the field name that I'm getting the value for, which also returns it's own mock for `FieldItemListInterface` with a value set for the `getString()` method.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function should_return_an_asset_id(): void {
|
||||
// Arrange.
|
||||
|
@ -84,7 +84,7 @@ If I was to refactor from using the `get()` and `getString()` methods to a diffe
|
|||
|
||||
This is how I could write the same test using a kernel (integration) test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function should_return_an_asset_id(): void {
|
||||
// Arrange.
|
||||
|
|
|
@ -8,7 +8,7 @@ tags: [php]
|
|||
|
||||
Here's a snippet of some Drupal code that I wrote last week. It's responsible for converting an array of nodes into a Collection of one of it's field values.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
return Collection::make($stationNodes)
|
||||
->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString())
|
||||
->values();
|
||||
|
@ -28,7 +28,7 @@ It accepts the original node but checks to ensure that the node is the correct t
|
|||
|
||||
I've added a helper method to get the field value, encapsulating that logic in a reusable function whilst making the code easier to read and its intent clearer.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
namespace Drupal\mymodule\ValueObject;
|
||||
|
||||
use Drupal\node\NodeInterface;
|
||||
|
@ -60,7 +60,7 @@ final class Station implements StationInterface {
|
|||
|
||||
This is what my code now looks like:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
return Collection::make($stationNodes)
|
||||
->map(fn (NodeInterface $node): StationInterface => Station::fromNode($node))
|
||||
->map(fn (StationInterface $station): string => $station->getStationCode())
|
||||
|
|
|
@ -37,7 +37,7 @@ It also makes it more obvious to anyone reading the code that these values are b
|
|||
|
||||
It's similar to having this example PHP code:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
function __invoke(string $type, int $limit): void {};
|
||||
```
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ tags:
|
|||
|
||||
When writing object-orientated code, particularly in PHP, you usually write method names using camel-case letters - such as:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function doSomething(): void {
|
||||
// ...
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public function doSomething(): void {
|
|||
|
||||
This is also true when writing methods within a test class - only that the method name is prefixed with the word `test`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testSomething(): void {
|
||||
}
|
||||
```
|
||||
|
@ -27,7 +27,7 @@ This is probably expected and complies with the PSR code style standards like PS
|
|||
|
||||
Something that I've seen some PHP developers and some frameworks prefer is to write their test methods using snake-case letters and commonly removing the `test` prefix in favour of using an annotation:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function the_api_should_return_a_200_response_code_if_everything_is_ok(): void {
|
||||
// ...
|
||||
|
|
|
@ -14,7 +14,7 @@ As long as a class implements an Interface, it can be decorated.
|
|||
|
||||
For example, if I have this PHP interface:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
interface DoesSomething
|
||||
{
|
||||
public function doSomething(): void;
|
||||
|
@ -23,7 +23,7 @@ interface DoesSomething
|
|||
|
||||
I could have this class that does something:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
final class FirstClass implements DoesSomething
|
||||
{
|
||||
public function doSomething(): void
|
||||
|
@ -37,7 +37,7 @@ If I need to do something else, like caching or logging the result, I can decora
|
|||
|
||||
To do this, I need another class that implements the same interface and inject the original version.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
final class SecondClass implements DoesSomething
|
||||
{
|
||||
public function __constuct(
|
||||
|
|
|
@ -19,7 +19,7 @@ A local port needs to be exposed from the database container that Neovim can con
|
|||
|
||||
I usually do this with a `docker-compose.override.yaml` file:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
services:
|
||||
database:
|
||||
ports:
|
||||
|
|
|
@ -45,7 +45,7 @@ Now I can run `make composer COMPOSER_ARGS="info drupal/core"` and see what I wa
|
|||
|
||||
`just`, on the other hand, allows for defining parameters to its recipes:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
composer *args:
|
||||
docker compose exec php composer {{ args }}
|
||||
```
|
||||
|
|
|
@ -14,7 +14,7 @@ If I'm passing arguments into a constructor, I can declare a visibility and it w
|
|||
|
||||
Here's an example of a value of a data transfer object that accepts a sort code and account number as strings:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
class AccountDetails {
|
||||
|
||||
public function __construct(
|
||||
|
@ -27,7 +27,7 @@ class AccountDetails {
|
|||
|
||||
Without promoted constructor properties, I'd need to create the properties and assign them manually, and I'd have this:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
class AccountDetails {
|
||||
|
||||
public string $accountNumber;
|
||||
|
|
|
@ -10,7 +10,7 @@ tags:
|
|||
|
||||
Continuing with yesterday's data transfer object (DTO) example, something that can be done since PHP 8.1 is to make properties read-only:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
class AccountDetails {
|
||||
|
||||
public function __construct(
|
||||
|
@ -25,7 +25,7 @@ This means the public properties can be read and used without the need for gette
|
|||
|
||||
Without `readonly`, a DTO can be created and the property values can be changed:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$accountDetails = new AccountDetails('12345678', '00-00-00');
|
||||
$accountDetails->accountNumber = 'banana';
|
||||
```
|
||||
|
|
|
@ -13,7 +13,7 @@ A data transfer object only stores data but we could also use a value object tha
|
|||
|
||||
For example, we could validate that the account number is the correct length and the sort code is the correct format:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
class AccountDetails {
|
||||
|
||||
public function __construct(
|
||||
|
|
|
@ -12,7 +12,7 @@ Marian Kostadinov ([stochnagara on Twitter](https://twitter.com/stochnagara)) re
|
|||
|
||||
Looking at the previous class:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
class AccountDetails {
|
||||
|
||||
public function __construct(
|
||||
|
@ -25,7 +25,7 @@ class AccountDetails {
|
|||
|
||||
Instead of setting each property as `readonly`, the whole class can instead be marked as `readonly`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
readonly class AccountDetails {
|
||||
|
||||
public function __construct(
|
||||
|
|
|
@ -17,7 +17,7 @@ Why? I didn't like the inconsistency of using one approach for variable names an
|
|||
|
||||
I'd have had code like this with a mixture of both:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
class MyClass {
|
||||
|
||||
private EntityTypeManagerInterface $entityTypeManager;
|
||||
|
@ -31,7 +31,7 @@ class MyClass {
|
|||
|
||||
Or even more simply:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
```
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ I've seen a lot on social media and posts and videos recently about Laravel Pipe
|
|||
|
||||
This is an example from the new documentation:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$user = Pipeline::send($user)
|
||||
->through([
|
||||
GenerateProfilePhoto::class,
|
||||
|
|
|
@ -21,7 +21,7 @@ Maybe a user was pending initially, and they're active after running a command o
|
|||
|
||||
To help me get started, I'll sometimes write a test like this with placeholders to separate the test into its separate stages:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
function should_activate_a_pending_user(): void {
|
||||
// Arrange.
|
||||
|
@ -38,7 +38,7 @@ This makes me think about the different stages and what each might need to conta
|
|||
|
||||
Or I might write it out in the "Given, When, Then" format:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
function should_activate_a_pending_user(): void {
|
||||
// Given I have a user.
|
||||
|
|
|
@ -16,7 +16,7 @@ For example, the `drupal_set_message()` function was deprecated in Drupal 8.5 an
|
|||
|
||||
Once it was deprecated, the function was changed to use the new service to avoid duplicating code and a message was added to notify Developers:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) {
|
||||
@trigger_error('drupal_set_message() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ I'm using PHPUnit's native assertions to check it returns a Collection (I regula
|
|||
|
||||
My initial implementation was to loop over each node and use `assertSame` on its bundle before refactoring to create an array of unique bundle names and comparing it to my expected names:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertSame(
|
||||
expected: [$nodeType],
|
||||
actual: $haystack
|
||||
|
|
|
@ -13,7 +13,7 @@ Writing custom assertions is a great way to clean up your test code.
|
|||
|
||||
Here's an example from one of my client Drupal projects:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
private static function assertProductVariationHasPrice(ProductVariationInterface $productVariation, string $expectedPrice): void {
|
||||
self::assertSame(
|
||||
actual: $productVariation->getPrice()->getNumber(),
|
||||
|
|
|
@ -14,7 +14,7 @@ In PHP, this is done by throwing an Exception if a condition is met.
|
|||
|
||||
For example:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
if (!is_array(false)) {
|
||||
throw new \Exception('Not an array');
|
||||
}
|
||||
|
@ -22,13 +22,13 @@ if (!is_array(false)) {
|
|||
|
||||
There's also the `assert` construct which, since PHP 8.0, throws an Exception by default:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
assert(is_array(false));
|
||||
```
|
||||
|
||||
You can also use an assertion library, such as `webmozart/assert` or `beberlei/assert` which provide assertions and guard methods:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
Assert::isArray(false);
|
||||
|
|
|
@ -12,7 +12,7 @@ Following yesterday's email about input validation, guard clauses and assertion
|
|||
|
||||
For example:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
function createJourney(string $from, string $to, int $duration): void {
|
||||
var_dump($from, $to, $duration);
|
||||
}
|
||||
|
@ -22,13 +22,13 @@ In this code, each parameter has a type, but there's no validation on the values
|
|||
|
||||
If I run this:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
createJourney('', '', -10);
|
||||
```
|
||||
|
||||
I would get this output:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
string(0) ""
|
||||
string(0) ""
|
||||
int(-10)
|
||||
|
@ -44,7 +44,7 @@ I can use an assertion library or throw my own Exceptions if the values pass the
|
|||
|
||||
For example:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
function createJourney(string $from, string $to, int $duration): void {
|
||||
Assert::stringNotEmpty($from);
|
||||
Assert::stringNotEmpty($to);
|
||||
|
|
|
@ -26,7 +26,7 @@ To start, we wrote a test for existing functionality within Drupal core - anonym
|
|||
|
||||
This is the whole test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\my_module\Functional;
|
||||
|
|
|
@ -18,7 +18,7 @@ If I look at some code, I want to see as few indentation levels as possible, mak
|
|||
Instead, you check for a condition; if that isn't met, you return early.
|
||||
For example, here is some code I saw recently during a meetup talk:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$callingClass = $scope->getClassReflection()->getName();
|
||||
|
||||
if ($callingClass === TextMessageQueueProcessor::class) {
|
||||
|
|
|
@ -23,7 +23,7 @@ Only write comments that add value, and if there are fewer comments, they are ea
|
|||
|
||||
This is an example from an open source pull request:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* An interface for field access override.
|
||||
*/
|
||||
|
|
|
@ -38,7 +38,7 @@ For example, to install Pathauto, you run `composer require drupal/pathauto`.
|
|||
|
||||
Within its output, you'll see this:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
Package operations: 3 installs, 0 updates, 0 removals
|
||||
- Downloading drupal/token (1.12.0)
|
||||
- Downloading drupal/ctools (4.0.4)
|
||||
|
|
|
@ -16,7 +16,7 @@ Here's an example (thanks, ChatGPT, for the code).
|
|||
|
||||
## The Class to be tested (MyClass.php)
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
class MyClass {
|
||||
|
@ -36,7 +36,7 @@ class MyClass {
|
|||
|
||||
## Dependency Interface (DependencyInterface.php)
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
interface DependencyInterface {
|
||||
|
@ -48,7 +48,7 @@ interface DependencyInterface {
|
|||
|
||||
## A test class that ends up testing mocks (MyClassTest.php)
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
|
@ -16,7 +16,7 @@ In PHPUnit, there are different ways to write test methods.
|
|||
|
||||
The standard way is to use camel-case method names with a `test` prefix, for example:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testTheProjectNameShouldBeAString(): void
|
||||
{
|
||||
// ...
|
||||
|
@ -25,7 +25,7 @@ public function testTheProjectNameShouldBeAString(): void
|
|||
|
||||
Another popular way, particularly in some frameworks, is to use snake-case method names:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function the_project_name_should_be_a_string(): void
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ Unfortunately, they're often overused and are outdated compared to the code they
|
|||
|
||||
For example, here's some PHP code:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
function sayHello(string $name, array $options = ['shout' => false]): void {
|
||||
echo 'Hello, ' . $name;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ Then you'll get this error:
|
|||
|
||||
Now, we can use a docblock to provide more information to PHPStan to describe the structure of the `$options` array and that it has strings for keys and boolean values.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @param array<string, bool> $options
|
||||
*/
|
||||
|
@ -61,7 +61,7 @@ Now, we can use a docblock to provide more information to PHPStan to describe th
|
|||
|
||||
Although it's not erroring, we can add the `$name` parameter and declare it as a `string`.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array<string, bool> $options
|
||||
|
@ -78,7 +78,7 @@ We don't want an empty string for a name and want the correct options and values
|
|||
|
||||
Let's change the docblock to this:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @param non-empty-string $name
|
||||
* @param array{shout: bool} $options
|
||||
|
|
|
@ -16,7 +16,7 @@ As well as different base classes for types of tests - i.e. functional, kernel a
|
|||
|
||||
For example, if we have this test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\example\Kernel;
|
||||
|
@ -62,7 +62,7 @@ When writing a lot of tests, this can result in duplication and more complex tes
|
|||
|
||||
This can be simplified using `EntityKernelTestBase` instead of `KernelTestBase`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
|
|
|
@ -41,7 +41,7 @@ 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:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
name: Example
|
||||
type: module
|
||||
core_version_requirement: ^10
|
||||
|
@ -58,7 +58,7 @@ Run `mkdir -p web/modules/custom/atdc/tests/src/Functional && touch web/modules/
|
|||
|
||||
Then, add this content.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\atdc\Functional;
|
||||
|
@ -77,7 +77,7 @@ Note: within a test class, the namespace is `Drupal\Tests\{module_name}` instead
|
|||
|
||||
With the boilerplate class added, create a test method within it:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testBasic(): void {
|
||||
self::assertTrue(FALSE);
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ As you're writing functional tests by extending `BrowserTestBase`, you can make
|
|||
|
||||
Replace the `testBasic` test method with the following:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testFrontPage(): void {
|
||||
$this->drupalGet('/');
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ In some instances, setting zero will return all items - essentially, an 'unlimit
|
|||
|
||||
I imagine the code looks something like this:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
if ($limit > 0) {
|
||||
$query->range(0, $limit);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ It means the value could be either an integer or null, but I think the intent of
|
|||
|
||||
This would make the code look like this:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
if (is_int($limit)) {
|
||||
$query->range(0, $limit);
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ It wouldn't make sense to set a negative number as the limit or, as the unlimite
|
|||
|
||||
This is the end code I'd likely write:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
if (is_int($limit)) {
|
||||
if ($limit < 1) {
|
||||
throw new InvalidArgumentException('A limit must be a positive integer.');
|
||||
|
|
|
@ -17,14 +17,14 @@ The default approach is that all files can be added, and you specify the files a
|
|||
|
||||
For example, if my `.gitignore` file was this, these two directories would be ignored:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
vendor
|
||||
web
|
||||
```
|
||||
|
||||
The other approach is to ignore everything and unignore the things to add. For example:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
*
|
||||
!build.yaml
|
||||
!Dockerfile
|
||||
|
|
|
@ -27,7 +27,7 @@ Anything that affects multiple users - such as ignoring `vendor` or `node_module
|
|||
|
||||
Add this to your `~/.gitconfig` or `~/.config/git/config` file to set the path for your global ignore file:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
[core]
|
||||
excludesFile = "~/.config/git/ignore"
|
||||
```
|
||||
|
|
|
@ -23,7 +23,7 @@ Going forward, I'd like to ensure that each Drupal module only uses its own clas
|
|||
|
||||
Here's what I have so far for my [testing course codebase][atdc]:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
final class ArchitectureTest {
|
||||
|
||||
public function test_classes_should_be_final(): Rule {
|
||||
|
|
|
@ -37,8 +37,12 @@
|
|||
<meta property="twitter:title" content="{{ page.title }} | {{ site.name }}" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block styles %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -3,3 +3,20 @@
|
|||
{% block content_bottom %}
|
||||
{% include 'about-me.html.twig' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/github-dark.min.css">
|
||||
|
||||
<style type="text/css">
|
||||
.hljs {
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
|
||||
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/languages/php.min.js"></script>
|
||||
|
||||
<script>hljs.highlightAll();</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -30,7 +30,7 @@ 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:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
name: ATDC
|
||||
type: module
|
||||
core_version_requirement: ^10
|
||||
|
@ -47,7 +47,7 @@ Run `mkdir -p web/modules/custom/atdc/tests/src/Functional && touch web/modules/
|
|||
|
||||
Then, add this content.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\atdc\Functional;
|
||||
|
@ -66,7 +66,7 @@ Note: within a test class, the namespace is `Drupal\Tests\{module_name}` instead
|
|||
|
||||
With the boilerplate class added, create a test method within it:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testBasic(): void {
|
||||
self::assertTrue(FALSE);
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ As you're writing functional tests by extending `BrowserTestBase`, you can make
|
|||
|
||||
Replace the `testBasic` test method with the following:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testFrontPage(): void {
|
||||
$this->drupalGet('/');
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ Let's see how that would look as a unit test.
|
|||
|
||||
Create a new test, `PostNodeRepositoryUnitTest` and, for now, just create a new `PostNodeRepository`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
// web/modules/custom/atdc/tests/src/Unit/PostNodeRepositoryUnitTest.php
|
||||
|
@ -43,7 +43,7 @@ But, as this is a unit test, you can't get the Repository from the service conta
|
|||
|
||||
Try to fix this by creating a new `EntityTypeManager` and injecting it into the constructor:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$repository = new PostNodeRepository(
|
||||
new EntityTypeManager(),
|
||||
);
|
||||
|
@ -61,7 +61,7 @@ Instead of doing this manually, let's start using mocks.
|
|||
|
||||
Add `use Drupal\Core\Entity\EntityTypeManagerInterface;` and create a mock to use instead of the manually created version.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$repository = new PostNodeRepository(
|
||||
$this->createMock(EntityTypeManagerInterface::class),
|
||||
);
|
||||
|
@ -73,7 +73,7 @@ As the mock implements `EntityTypeManagerInterface`, this will fix the failure,
|
|||
|
||||
Next, try to get the posts from the Repository:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$repository->findAll();
|
||||
```
|
||||
|
||||
|
@ -87,7 +87,7 @@ For the test to work, this needs to be mocked too and returned from the `getStor
|
|||
|
||||
Create a mock of `EntityStorageInterface`, which will be used as the node storage:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$nodeStorage = $this->createMock(EntityStorageInterface::class);
|
||||
```
|
||||
|
||||
|
@ -95,7 +95,7 @@ Next, this needs to be returns from the mock `EntityTypeManager`.
|
|||
|
||||
To do this, specify that the `getStorage()` method when called with the value `node`, will return the mocked node storage:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
|
||||
$entityTypeManager->method('getStorage')->with('node')->willReturn($nodeStorage);
|
||||
|
||||
|
@ -112,7 +112,7 @@ You'll need to use a mock for each node and set what each method needs to return
|
|||
|
||||
The same as the Kernel test, set a title for each post with different created times.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$node1 = $this->createMock(NodeInterface::class);
|
||||
$node1->method('bundle')->willReturn('post');
|
||||
$node1->method('getCreatedTime')->willReturn(strtotime('-1 week'));
|
||||
|
@ -131,7 +131,7 @@ $node3->method('label')->willReturn('Post three');
|
|||
|
||||
Then, specify the `loadByProperties` method should return the posts.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$nodeStorage->method('loadByProperties')->willReturn([
|
||||
$node1,
|
||||
$node2,
|
||||
|
@ -141,7 +141,7 @@ $nodeStorage->method('loadByProperties')->willReturn([
|
|||
|
||||
Finally, add some assertions that the nodes returned are the correct ones and in the correct order:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$posts = $repository->findAll();
|
||||
|
||||
self::assertContainsOnlyInstancesOf(NodeInterface::class, $posts);
|
||||
|
@ -166,7 +166,7 @@ This is testing the same thing as the kernel test, but it's your preference whic
|
|||
|
||||
Hopefully, if you run your whole testsuite, you should see output like this:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
PHPUnit 9.6.15 by Sebastian Bergmann and contributors.
|
||||
|
||||
......... 9 / 9 (100%)
|
||||
|
@ -176,7 +176,7 @@ Time: 00:07.676, Memory: 10.00 MB
|
|||
|
||||
Or, if you use `--testdox`, output like this:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
PHPUnit 9.6.15 by Sebastian Bergmann and contributors.
|
||||
|
||||
Blog Page (Drupal\Tests\atdc\Functional\BlogPage)
|
||||
|
|
|
@ -22,7 +22,7 @@ For example, how do we test the administration pages to see if they work for a u
|
|||
|
||||
Let's start with a new test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testAdminPageLoggedIn(): void {
|
||||
$this->drupalGet('/admin');
|
||||
|
||||
|
@ -47,7 +47,7 @@ This is commonly known as the **Arrange** step of the test.
|
|||
|
||||
To create a user, use `$this->drupalCreateUser()` and `$this->drupalLogin()` to log in as that user.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$user = $this->drupalCreateUser();
|
||||
|
||||
$this->drupalLogin($user);
|
||||
|
@ -61,7 +61,7 @@ As we're testing against a temporary Drupal installation, we don't have access t
|
|||
|
||||
To do this, when creating the user, include an array of permissions to add to it:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$user = $this->createUser(permissions: [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
|
@ -80,7 +80,7 @@ Let's create a page and test we can view it.
|
|||
|
||||
Firstly, let's ensure the page is not found:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testContent(): void {
|
||||
$this->drupalGet('/node/1');
|
||||
$this->assertSession()->statusCodeEquals(Response::HTTP_NOT_FOUND);
|
||||
|
@ -91,7 +91,7 @@ Similar to `$this->createUser()`, there are similar methods to create content ty
|
|||
|
||||
Again, as there are no existing content or content types, we need to create them and add the follow-up assertions:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testContent(): void {
|
||||
// ...
|
||||
|
||||
|
@ -111,7 +111,7 @@ You're probably expecting the test to pass now, but you'll likely get an error l
|
|||
|
||||
To fix this, we need to tell Drupal to enable the `node` module within the test by adding this within the test class:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
protected static $modules = ['node'];
|
||||
```
|
||||
|
||||
|
@ -123,7 +123,7 @@ Here's a tip for today: if you're getting an unexpected status code or another e
|
|||
|
||||
To do that, add this to your test, and it will output the page content:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
var_dump($this->getSession()->getPage()->getContent());
|
||||
```
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ Create a new `BlogPageTest` and have it extend `BrowserTestBase`.
|
|||
|
||||
Let's assert that a page should exist at `/blog` by returning a `200` status code, as this should be accessible by anonymous users.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\atdc\Functional;
|
||||
|
@ -49,7 +49,7 @@ Whilst you could create the page using the Views module, let's create a custom r
|
|||
|
||||
Create an `atdc.routing.yml` file:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
# web/modules/custom/atdc/atdc.routing.yml
|
||||
|
||||
atdc.blog:
|
||||
|
@ -65,13 +65,13 @@ With this added, the status code doesn't change and is a `404`.
|
|||
|
||||
Like in the previous lesson, you need to enable the `atdc` module by setting `$modules` in your test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
protected static $modules = ['atdc'];
|
||||
```
|
||||
|
||||
You'll also need to create an `atdc.info.yml` file so the module can be installed:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
# web/modules/custom/atdc/atdc.info.yml
|
||||
|
||||
name: ATDC
|
||||
|
@ -81,7 +81,7 @@ core_version_requirement: ^10
|
|||
|
||||
This should change the status code to a `403`, as you also need the `node` module for the `access content` permission:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
protected static $modules = ['node', 'atdc'];
|
||||
```
|
||||
|
||||
|
@ -97,7 +97,7 @@ Let's do that next.
|
|||
|
||||
Create the expected Controller class within a `src/Controller` directory:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
// web/modules/custom/atdc/src/Controller/BlogPageController.php
|
||||
|
@ -115,7 +115,7 @@ For this step, the simplest thing you can do to get a passing test is to return
|
|||
|
||||
As long as it's an array, even an empty one, the test should pass:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function __invoke(): array {
|
||||
return [];
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ This is also a good time to do a `git commit`.
|
|||
|
||||
Again, let's start with a new test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testPostsAreVisible(): void {
|
||||
// Arrange.
|
||||
$this->createNode(['type' => 'post', 'title' => 'First post']);
|
||||
|
@ -165,7 +165,7 @@ Start by extending the `ControllerBase` base class within your Controller:
|
|||
|
||||
Now, within the `__invoke` method, add this to return a list of each node title:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function __invoke(): array {
|
||||
$nodeStorage = $this->entityTypeManager()->getStorage('node');
|
||||
$nodes = $nodeStorage->loadMultiple();
|
||||
|
|
|
@ -26,7 +26,7 @@ This will contain the `PostNodeRepository` class that will be responsible for lo
|
|||
|
||||
Add this as the initial content:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\atdc\Repository;
|
||||
|
@ -62,7 +62,7 @@ $nodes = $nodeStorage->loadMultiple();
|
|||
|
||||
Add them to the `findAll()` method, alter the first line that gets the `EntityTypeManager` (we'll refactor this later) and return the loaded nodes:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function findAll(): array {
|
||||
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
$nodes = $nodeStorage->loadMultiple();
|
||||
|
@ -73,7 +73,7 @@ public function findAll(): array {
|
|||
|
||||
Within the `BlogPageController`, create a constructor method and inject the Repository using constructor property promotion:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function __construct(
|
||||
private PostNodeRepository $postNodeRepository,
|
||||
) {
|
||||
|
@ -82,7 +82,7 @@ public function __construct(
|
|||
|
||||
Add `use Drupal\atdc\Repository\PostNodeRepository;` if needed, and use it to load the post nodes:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
|
||||
public function __invoke(): array {
|
||||
$nodes = $this->postNodeRepository->findAll();
|
||||
|
@ -105,7 +105,7 @@ Currently, the test is failing, as the response code is a `500` status because t
|
|||
|
||||
It's expected within the constructor, but you must add a `create` method to inject it.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public static function create(ContainerInterface $container): self {
|
||||
return new self(
|
||||
$container->get(PostNodeRepository::class),
|
||||
|
@ -123,7 +123,7 @@ To do this, create an `atdc.services.yml` file within your module.
|
|||
|
||||
Add `PostNodeRepository` using the fully-qualified class name as the service name:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
services:
|
||||
Drupal\atdc\Repository\PostNodeRepository:
|
||||
arguments: []
|
||||
|
@ -139,7 +139,7 @@ Before moving on, let's refactor the `PostNodeRepository` and inject the `Entity
|
|||
|
||||
The same as the `BlogPageController`, create a constructor method and inject the `EntityTypeManagerInterface`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function __construct(
|
||||
private EntityTypeManagerInterface $entityTypeManager,
|
||||
) {
|
||||
|
@ -148,7 +148,7 @@ public function __construct(
|
|||
|
||||
Add the `use Drupal\Core\Entity\EntityTypeManagerInterface;` if needed, and specify it as an argument so it's injected into the constructor:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
services:
|
||||
Drupal\atdc\Repository\PostNodeRepository:
|
||||
arguments:
|
||||
|
|
|
@ -28,7 +28,7 @@ Instead of making HTTP requests and checking the responses, we can test the resu
|
|||
|
||||
Let's create a new test that uses the `PostNodeRepository` to find the nodes and assert we get an expected number returned.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\atdc\Kernel;
|
||||
|
@ -73,7 +73,7 @@ It's defined within `atdc.services.yml`, but we need to be explicit about which
|
|||
|
||||
Create a `$modules` array within the test class and add `atdc`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
protected static $modules = ['atdc'];
|
||||
```
|
||||
|
||||
|
@ -83,7 +83,7 @@ Run the tests again, and you should get a different error:
|
|||
|
||||
As well as `atdc`, you must enable the `node` module. You can do so by adding it to the `$modules` array:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
protected static $modules = ['node', 'atdc'];
|
||||
```
|
||||
|
||||
|
@ -111,7 +111,7 @@ Within the test, you can use `$this->createNode()` to create posts.
|
|||
|
||||
Create the three posts the test is expecting:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
// Arrange.
|
||||
$this->createNode(['type' => 'post']);
|
||||
$this->createNode(['type' => 'post']);
|
||||
|
@ -126,7 +126,7 @@ Next, let's assert they're returned in a specific order.
|
|||
|
||||
Update the posts to have a specific title and created date so we can specify which order we expect them to be returned in and which titles they should have:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
// Arrange.
|
||||
$this->createNode([
|
||||
'created' => (new DrupalDateTime('-1 week'))->getTimestamp(),
|
||||
|
@ -151,7 +151,7 @@ Note we're intentionally setting them to be in an incorrect order, to begin with
|
|||
|
||||
Next, assert that the titles are returned in the correct order.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertSame(
|
||||
['Post two', 'Post one', 'Post three'],
|
||||
array_map(
|
||||
|
@ -165,7 +165,7 @@ For each node in `$nodes`, get its label (title) and compare them with the title
|
|||
|
||||
As expected, the test fails:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate
|
||||
Failed asserting that two arrays are identical.
|
||||
--- Expected
|
||||
|
@ -187,7 +187,7 @@ We need to update the code within `PostNodeRepository` to fix the ordering.
|
|||
|
||||
After loading the nodes, we need to sort them.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function findAll(): array {
|
||||
$nodeStorage = $this->entityTypeManager->getStorage('node');
|
||||
$nodes = $nodeStorage->loadMultiple();
|
||||
|
@ -206,7 +206,7 @@ This gets us further, but the test is still failing.
|
|||
|
||||
Whilst the order is correct, the array keys don't match what we expect:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate
|
||||
Failed asserting that two arrays are identical.
|
||||
--- Expected
|
||||
|
@ -233,7 +233,7 @@ And, because the correct titles are still being shown, our original Functional t
|
|||
|
||||
Tip: to see the names of the tests in your output, add the `--testdox` flag to the `phpunit` command:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
Blog Page (Drupal\Tests\atdc\Functional\BlogPage)
|
||||
✔ Blog page
|
||||
✔ Posts are visible
|
||||
|
|
|
@ -12,7 +12,7 @@ In yesterday's lesson, you created your first Kernel test and used it to ensure
|
|||
|
||||
This is how we're creating the posts currently:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$this->createNode([
|
||||
'created' => (new DrupalDateTime('-1 week'))->getTimestamp(),
|
||||
'title' => 'Post one',
|
||||
|
@ -40,7 +40,7 @@ Let's create a Builder class to create the posts.
|
|||
|
||||
This is how I'd like to create a post using a `PostBuilder`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
PostBuilder::create()
|
||||
->setCreatedDate('-1 week')
|
||||
->setTitle('Post one')
|
||||
|
@ -51,7 +51,7 @@ This makes it easier to do by creating named methods for each value we want to s
|
|||
|
||||
To do this, create a new class at `src/Builder/PostBuilder.php`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
// web/modules/custom/atdc/src/Builder/PostBuilder.php
|
||||
|
@ -73,7 +73,7 @@ As it returns a new version of `self`, you can also chain methods onto it.
|
|||
|
||||
Add the additional methods and properties:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
private ?DrupalDateTime $created = NULL;
|
||||
|
||||
private string $title;
|
||||
|
@ -95,7 +95,7 @@ Again, by returning `$this`, we can keep chaining methods.
|
|||
|
||||
Finally, create the `getPost()` method that creates the node based on the property values, saves it, and returns it.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function getPost(): NodeInterface {
|
||||
$post = Node::create([
|
||||
'created' => $this->created?->getTimestamp(),
|
||||
|
@ -111,7 +111,7 @@ public function getPost(): NodeInterface {
|
|||
|
||||
Now, refactor the test to use the `PostBuilder`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
PostBuilder::create()
|
||||
->setCreatedDate('-1 week')
|
||||
->setTitle('Post one')
|
||||
|
@ -136,7 +136,7 @@ Finally, for today, let's refactor the assertion that verifies the titles are re
|
|||
|
||||
This is the current assertion:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertSame(
|
||||
['Post two', 'Post one', 'Post three'],
|
||||
array_map(
|
||||
|
@ -152,7 +152,7 @@ We can make this more reusable and readable by extracting this into a new custom
|
|||
|
||||
Create a new static function at the bottom of the class with a name that describes what it's asserting:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @param array<int, string> $expectedTitles
|
||||
* @param array<int, NodeInterface> $nodes
|
||||
|
@ -179,7 +179,7 @@ The benefits are that this now has a name that describes what we're asserting, a
|
|||
|
||||
Finally, refactor the test to use the new assertion:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertNodeTitlesAreSame(
|
||||
['Post two', 'Post one', 'Post three'],
|
||||
$nodes,
|
||||
|
|
|
@ -20,7 +20,7 @@ First, let's ensure that only published nodes are returned and displayed on the
|
|||
|
||||
We can do this easily with a functional test, so add a new test method to `BlogPostTest`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testOnlyPublishedNodesAreShown(): void {
|
||||
PostBuilder::create()
|
||||
->setTitle('Post one')
|
||||
|
@ -54,7 +54,7 @@ In this test, we want to create some published and unpublished posts and assert
|
|||
|
||||
To fix the error, add this function so it exists:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function isPublished(): self {
|
||||
return $this;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ When using `PostBuilder` in the previous lesson, we were always providing a crea
|
|||
|
||||
Update the `getPost()` method to only set the created time if the `created` property has a value.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function getPost(): NodeInterface {
|
||||
$post = Node::create([
|
||||
'title' => $this->title,
|
||||
|
@ -93,7 +93,7 @@ Now, we can see a similar error to the one before for `isNotPublished()`.
|
|||
|
||||
Again, create the simplest version of the method so the test can progress:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function isNotPublished(): self {
|
||||
return $this;
|
||||
}
|
||||
|
@ -112,13 +112,13 @@ Within `PostBuilder`, we need to use the `isPublished` and `isNotPublished` meth
|
|||
|
||||
First, add an `isPublished` property to the class and set it to be `TRUE` by default:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
private bool $isPublished = TRUE;
|
||||
```
|
||||
|
||||
Next, update the `isPublished()` and `isNotPublished()` methods to set the value appropriately:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function isNotPublished(): self {
|
||||
$this->isPublished = FALSE;
|
||||
|
||||
|
@ -136,7 +136,7 @@ Even though `isPublished` is already true by default, doing this makes it explic
|
|||
|
||||
Finally, within `getPost()`, update the code that creates the node to set the `status` property accordingly.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$post = Node::create([
|
||||
'status' => $this->isPublished,
|
||||
'title' => $this->title,
|
||||
|
@ -152,7 +152,7 @@ We also need to update the `PostNodeRepository` as that is responsible for loadi
|
|||
|
||||
Currently, all we're doing is this:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$nodes = $nodeStorage->loadMultiple();
|
||||
```
|
||||
|
||||
|
@ -160,7 +160,7 @@ This will load all nodes, regardless of their type or status.
|
|||
|
||||
To fix this, change this to use `loadByProperties()` instead:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$nodes = $nodeStorage->loadByProperties();
|
||||
```
|
||||
|
||||
|
@ -170,7 +170,7 @@ Note: you can also use `->getQuery()` if you prefer and write the query yourself
|
|||
|
||||
For this case, let's add a property for `status` and its value to be `TRUE`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$nodes = $nodeStorage->loadByProperties([
|
||||
'status' => TRUE,
|
||||
]);
|
||||
|
@ -184,7 +184,7 @@ The other issue is all published nodes are returned, even if they aren't posts.
|
|||
|
||||
Before adding this to `PostNodeRepository`, create a new failing test for it:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
public function testOnlyPostNodesAreShown(): void {
|
||||
PostBuilder::create()->setTitle('Post one')->getPost();
|
||||
PostBuilder::create()->setTitle('Post two')->getPost();
|
||||
|
@ -213,7 +213,7 @@ If you run the test, it should fail as expected:
|
|||
|
||||
Now we have a failing test, let's add the extra condition to `PostNodeRepository`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$nodes = $nodeStorage->loadByProperties([
|
||||
'status' => TRUE,
|
||||
'type' => 'post',
|
||||
|
|
|
@ -12,7 +12,7 @@ In this lesson, let's add tags to our posts using the `PostBuilder`.
|
|||
|
||||
As we're doing test-driven development, start by creating a new `PostBuilderTest`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
// web/modules/custom/atdc/tests/src/Kernel/Builder/PostBuilderTest.php
|
||||
|
@ -37,7 +37,7 @@ Let's start by testing the existing functionality within `PostBuilder` by verify
|
|||
|
||||
Create these tests, which should pass by default as the code is already written:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function it_returns_a_published_post(): void {
|
||||
$node = PostBuilder::create()
|
||||
|
@ -73,7 +73,7 @@ Next, create a test for adding tags to a post.
|
|||
|
||||
It should be mostly the same as the others, but instead of an assertion for the published status, try to use `var_dump()` to see the value of `field_tags`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function it_returns_a_post_with_tags(): void {
|
||||
$node = PostBuilder::create()
|
||||
|
@ -104,7 +104,7 @@ The convention is to create a test module that will only be required within the
|
|||
|
||||
To do this, create a `web/modules/custom/atdc/modules/atdc_test` directory and an `atdc_test.info.yml` file with this content:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
name: ATDC Test
|
||||
type: module
|
||||
core_version_requirement: ^10
|
||||
|
@ -123,7 +123,7 @@ Rather than trying to write them by hand, I create the configuration I need, suc
|
|||
|
||||
To do that for this project, as I'm using the PHP built-in web server, I can use Drush to install Drupal using an SQLite database:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
./vendor/bin/drush site:install --db-url sqlite://localhost/atdc.sqlite
|
||||
```
|
||||
|
||||
|
@ -143,7 +143,7 @@ These are the files that I created in my module based on the field I created.
|
|||
|
||||
`field.field.node.post.field_tags.yml`:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
|
@ -174,7 +174,7 @@ field_type: entity_reference
|
|||
|
||||
`field.storage.node.field_tags.yml`:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
|
@ -198,7 +198,7 @@ custom_storage: false
|
|||
|
||||
Then, enable the module within `PostBuilderTest`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
protected static $modules = [
|
||||
// Core.
|
||||
'node',
|
||||
|
@ -210,7 +210,7 @@ protected static $modules = [
|
|||
|
||||
Finally, install the configuration to create the field. Add this within the test:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$this->installConfig(modules: [
|
||||
'atdc_test',
|
||||
]);
|
||||
|
@ -218,7 +218,7 @@ $this->installConfig(modules: [
|
|||
|
||||
After adding this and attempting to install the configuration to add the field, you'll get an error:
|
||||
|
||||
```plain
|
||||
```language-plain
|
||||
Exception when installing config for module atdc_test, the message was: Field 'field_tags' on entity type 'node' references a target entity type 'taxonomy_term', which does not exist.
|
||||
```
|
||||
|
||||
|
@ -234,7 +234,7 @@ As well as the field configuration, we also need to create the Post content type
|
|||
|
||||
This can be done by creating a `node.type.post.yml` file:
|
||||
|
||||
```yaml
|
||||
```language-yaml
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
|
@ -255,7 +255,7 @@ Let's update the test and add assertions about the tags being saved and returned
|
|||
|
||||
Get the tags from the post and assert that three tags are returned:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$tags = $node->get('field_tags')->referencedEntities();
|
||||
self::assertCount(3, $tags);
|
||||
```
|
||||
|
@ -264,7 +264,7 @@ As none have been added, this would fail the test.
|
|||
|
||||
Update the test to use a `setTags()` method that you haven't created yet:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$node = PostBuilder::create()
|
||||
->setTitle('test')
|
||||
->setTags(['Drupal', 'PHP', 'Testing'])
|
||||
|
@ -277,7 +277,7 @@ You should get an error confirming the method is undefined:
|
|||
|
||||
To fix this, add the `tags` property and `setTags()` method to `PostBuilder`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
|
@ -297,7 +297,7 @@ Tags will be an array of strings, and `setTags()` should set the tags to the `ta
|
|||
|
||||
Next, add the logic to `getPost()` to create a taxonomy term for each tag name.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$tagTerms = [];
|
||||
|
||||
if ($this->tags !== []) {
|
||||
|
@ -322,7 +322,7 @@ If `$this->tags` is not empty, create a new taxonomy term for each one and save
|
|||
|
||||
As well as asserting we have the correct number of tags, let's also assert that the correct tag names are returned and that they are the correct type of term.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertContainsOnlyInstancesOf(TermInterface::class, $tags);
|
||||
foreach ($tags as $tag) {
|
||||
self::assertSame('tags', $tag->bundle());
|
||||
|
@ -333,7 +333,7 @@ To assert the tags array only includes taxonomy terms, use `self::assertContains
|
|||
|
||||
Next, add some new assertions to the test to check the tag names match the specified tags.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertSame('Drupal', $tags[0]->label());
|
||||
self::assertSame('PHP', $tags[1]->label());
|
||||
self::assertSame('Testing', $tags[2]->label());
|
||||
|
|
|
@ -22,7 +22,7 @@ I've also seen Unit tests that are very tightly coupled to the implementation, s
|
|||
|
||||
Based on what you've learned so far, let's write a Unit test that we'd expect to pass:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\atdc\Unit;
|
||||
|
@ -58,7 +58,7 @@ Update the test to create a mock version of `NodeInterface` instead.
|
|||
|
||||
As the mock an instance of `NodeInterface`, it satisfies the assertion and the test passes.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/** @test */
|
||||
public function it_wraps_a_post(): void {
|
||||
$node = $this->createMock(NodeInterface::class);
|
||||
|
@ -69,7 +69,7 @@ public function it_wraps_a_post(): void {
|
|||
|
||||
Next, add an assertion to ensure the bundle is correct:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
self::assertSame('post', $node->bundle());
|
||||
```
|
||||
|
||||
|
@ -81,7 +81,7 @@ Because you're using a mock, all methods will return `NULL`.
|
|||
|
||||
To get this to pass, you need to define what `$this->bundle()` will return:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$node->method('bundle')->willReturn('post');
|
||||
```
|
||||
|
||||
|
@ -97,7 +97,7 @@ Within the test, instantiate a new `Postwrapper` class that takes the node as an
|
|||
|
||||
Then, add an assertion that a `getType()` method should return `post`.
|
||||
|
||||
```php
|
||||
```language-php
|
||||
$wrapper = new PostWrapper($node);
|
||||
|
||||
self::assertSame('post', $wrapper->getType());
|
||||
|
@ -105,7 +105,7 @@ self::assertSame('post', $wrapper->getType());
|
|||
|
||||
Next, create a `PostWrapper` class with the `getType()` method:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
<?php
|
||||
|
||||
namespace Drupal\atdc;
|
||||
|
@ -132,7 +132,7 @@ We've tested that the `PostWrapper` works with post nodes, but let's also ensure
|
|||
|
||||
Create a new test that creates a mock node and returns `page` as the bundle:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @test
|
||||
* @testdox It can't wrap a page
|
||||
|
@ -153,7 +153,7 @@ Before creating a new `PostWrapper`, assert that an `InvalidArgumentException` s
|
|||
|
||||
To fix it, within the constructor for `PostWrapper`, check the bundle and throw the expected Exception if the bundle is not `post`:
|
||||
|
||||
```php
|
||||
```language-php
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue