oliverdavies.uk/content/node.3681f8cd-22d5-4c2c-b03f-a751bc39753d.json

91 lines
11 KiB
JSON
Raw Normal View History

2025-04-21 02:34:46 +01:00
{
"uuid": [
{
2025-05-11 09:40:11 +01:00
"value": "3681f8cd-22d5-4c2c-b03f-a751bc39753d"
2025-04-21 02:34:46 +01:00
}
],
"langcode": [
{
"value": "en"
}
],
"type": [
{
"target_id": "daily_email",
"target_type": "node_type",
"target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
}
],
"revision_timestamp": [
{
2025-05-11 09:40:11 +01:00
"value": "2025-05-11T09:00:55+00:00"
2025-04-21 02:34:46 +01: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": "Why I mostly write functional and integration tests"
}
],
"created": [
{
"value": "2022-09-16T00:00:00+00:00"
}
],
"changed": [
{
2025-05-11 09:40:11 +01:00
"value": "2025-05-11T09:00:55+00:00"
2025-04-21 02:34:46 +01:00
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"path": [
{
"alias": "\/daily\/2022\/09\/16\/why-mostly-write-functional-and-integration-tests",
2025-04-29 14:30:51 +01:00
"langcode": "en"
2025-04-21 02:34:46 +01:00
}
],
"body": [
{
2025-05-30 02:14:32 +01:00
"value": "\n <p>In <a href=\"\/daily\/2022\/09\/14\/simpletest-drupal-test\">Wednesday's email<\/a>, I showed how quick it is to get started writing automated tests for a new Drupal module, starting with a functional test.<\/p>\n\n<p>I prefer the outside-in style (or London approach) of test-driven development, where I start with a the highest-level test that I can for a task. If the task needs me to make a HTTP request, then I\u2019ll use a functional test. If not, I\u2019ll use a kernel (or integration) test.<\/p>\n\n<p>I find that these higher-level types of tests are easier and quicker to set up compared to starting with lower-level unit tests, cover more functionality, and make it easier to refactor.<\/p>\n\n<h2 id=\"an-example\">An example<\/h2>\n\n<p>For example, this <code>Device<\/code> class which is a data transfer object around Drupal's <code>NodeInterface<\/code>. 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:<\/p>\n\n<pre><code class=\"language-php\">final class Device {\n\n private NodeInterface $node;\n\n public function __construct(NodeInterface $node) {\n if ($node-&gt;bundle() != 'device') {\n throw new \\InvalidArgumentException();\n }\n\n $this-&gt;node = $node;\n }\n\n public function getAssetId(): string {\n return $this-&gt;node-&gt;get('field_asset_id')-&gt;getString();\n }\n\n public static function fromNode(NodeInterface $node): self {\n return new self($node);\n }\n\n}\n<\/code><\/pre>\n\n<h2 id=\"testing-getting-the-asset-id-using-a-unit-test\">Testing getting the asset ID using a unit test<\/h2>\n\n<p>As the <code>Node::create()<\/code> method (what I'd normally use to create a node) interacts with the database, I need to create a mock node to wrap with my DTO.<\/p>\n\n<p>I need to specify what value is returned from the <code>bundle()<\/code> method as well as getting the asset ID field value.<\/p>\n\n<p>I need to mock the <code>get()<\/code> method and specify the field name that I'm getting the value for, which also returns it's own mock for <code>FieldItemListInterface<\/code> with a value set for the <code>getString()<\/code> method.<\/p>\n\n<pre><code class=\"language-php\">\/** @test *\/\npublic function should_return_an_asset_id(): void {\n \/\/ Arrange.\n $fieldItemList = $this-&gt;createMock(FieldItemListInterface::class);\n\n $fieldItemList\n -&gt;method('getString')\n -&gt;willReturn('ABC');\n\n $deviceNode = $this-&gt;createMock(NodeInterface::class);\n\n $deviceNode\n -&gt;method('bundle')\n -&gt;willReturn('device');\n\n $deviceNode\n -&gt;method('get')\n -&gt;with('field_asset_id')\n -&gt;willReturn($fieldItemList);\n\n \/\/ Act.\n $device = Device::fromNode($deviceNode);\n\n \/\/ Assert.\n self::assertSame('ABC', $device-&gt;getAssetId());\n}\n<\/code><\/pre>\n\n<p>This is quite a long 'arrange' section for this test, and just be confusing for those new to automated testing.<\/p>\n\n<p>If I was to refactor from using the <code>get()<\/code> and <code>getString()<\/code> methods to a different implementation, it's likely that the test would fail.<\/p>\n\n<h2 id=\"refactoring-to-a-kernel-test\">Refactoring to a kernel test<\/h2>\n\n<p>This is how I could write the same test using a kernel (integration) test:<\/p>\n\n<pre><code class=\"language-php\">\/** @test *\/\npublic function should_return_an_asset_id(): void {\n \/\/ Arrange.\n $node = Node::create([\n 'field_asset_id' =&gt; 'ABC',\n 'type' =&gt; 'device'\n ]);\n\n \/\/ Assert.\n self::assertSame('ABC', Device::fromNode($node)-&gt;getAssetId());\n}\n<\/code><\/pre>\n\n<p>I can create a real <code>Node<\/code> object, pass that to the <code>Device<\/code> DTO, and call the <code>getAssetId()<\/code> method.<\/p>\n\n<p>As I can interact with the database, there's no need to create mocks or define return values.<\/p>\n\n<p>The 'arrange' step is much smaller, and I think that this is easier to read and understand.<\/p>\n\n<h3 id=\"
2025-04-21 02:34:46 +01:00
"format": "full_html",
2025-05-30 02:14:32 +01:00
"processed": "\n <p>In <a href=\"http:\/\/default\/daily\/2022\/09\/14\/simpletest-drupal-test\">Wednesday's email<\/a>, I showed how quick it is to get started writing automated tests for a new Drupal module, starting with a functional test.<\/p>\n\n<p>I prefer the outside-in style (or London approach) of test-driven development, where I start with a the highest-level test that I can for a task. If the task needs me to make a HTTP request, then I\u2019ll use a functional test. If not, I\u2019ll use a kernel (or integration) test.<\/p>\n\n<p>I find that these higher-level types of tests are easier and quicker to set up compared to starting with lower-level unit tests, cover more functionality, and make it easier to refactor.<\/p>\n\n<h2 id=\"an-example\">An example<\/h2>\n\n<p>For example, this <code>Device<\/code> class which is a data transfer object around Drupal's <code>NodeInterface<\/code>. 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:<\/p>\n\n<pre><code class=\"language-php\">final class Device {\n\n private NodeInterface $node;\n\n public function __construct(NodeInterface $node) {\n if ($node-&gt;bundle() != 'device') {\n throw new \\InvalidArgumentException();\n }\n\n $this-&gt;node = $node;\n }\n\n public function getAssetId(): string {\n return $this-&gt;node-&gt;get('field_asset_id')-&gt;getString();\n }\n\n public static function fromNode(NodeInterface $node): self {\n return new self($node);\n }\n\n}\n<\/code><\/pre>\n\n<h2 id=\"testing-getting-the-asset-id-using-a-unit-test\">Testing getting the asset ID using a unit test<\/h2>\n\n<p>As the <code>Node::create()<\/code> method (what I'd normally use to create a node) interacts with the database, I need to create a mock node to wrap with my DTO.<\/p>\n\n<p>I need to specify what value is returned from the <code>bundle()<\/code> method as well as getting the asset ID field value.<\/p>\n\n<p>I need to mock the <code>get()<\/code> method and specify the field name that I'm getting the value for, which also returns it's own mock for <code>FieldItemListInterface<\/code> with a value set for the <code>getString()<\/code> method.<\/p>\n\n<pre><code class=\"language-php\">\/** @test *\/\npublic function should_return_an_asset_id(): void {\n \/\/ Arrange.\n $fieldItemList = $this-&gt;createMock(FieldItemListInterface::class);\n\n $fieldItemList\n -&gt;method('getString')\n -&gt;willReturn('ABC');\n\n $deviceNode = $this-&gt;createMock(NodeInterface::class);\n\n $deviceNode\n -&gt;method('bundle')\n -&gt;willReturn('device');\n\n $deviceNode\n -&gt;method('get')\n -&gt;with('field_asset_id')\n -&gt;willReturn($fieldItemList);\n\n \/\/ Act.\n $device = Device::fromNode($deviceNode);\n\n \/\/ Assert.\n self::assertSame('ABC', $device-&gt;getAssetId());\n}\n<\/code><\/pre>\n\n<p>This is quite a long 'arrange' section for this test, and just be confusing for those new to automated testing.<\/p>\n\n<p>If I was to refactor from using the <code>get()<\/code> and <code>getString()<\/code> methods to a different implementation, it's likely that the test would fail.<\/p>\n\n<h2 id=\"refactoring-to-a-kernel-test\">Refactoring to a kernel test<\/h2>\n\n<p>This is how I could write the same test using a kernel (integration) test:<\/p>\n\n<pre><code class=\"language-php\">\/** @test *\/\npublic function should_return_an_asset_id(): void {\n \/\/ Arrange.\n $node = Node::create([\n 'field_asset_id' =&gt; 'ABC',\n 'type' =&gt; 'device'\n ]);\n\n \/\/ Assert.\n self::assertSame('ABC', Device::fromNode($node)-&gt;getAssetId());\n}\n<\/code><\/pre>\n\n<p>I can create a real <code>Node<\/code> object, pass that to the <code>Device<\/code> DTO, and call the <code>getAssetId()<\/code> method.<\/p>\n\n<p>As I can interact with the database, there's no need to create mocks or define return values.<\/p>\n\n<p>The 'arrange' step is much smaller, and I think that this is easier to read and understan
2025-04-21 02:34:46 +01:00
"summary": null
}
]
}