{"id":71481,"date":"2025-04-23T18:31:21","date_gmt":"2025-04-23T13:01:21","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=71481"},"modified":"2025-07-30T13:18:09","modified_gmt":"2025-07-30T07:48:09","slug":"how-to-run-phpunit-tests-efficiently-in-drupal","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/how-to-run-phpunit-tests-efficiently-in-drupal\/","title":{"rendered":"How to run PHPUnit tests efficiently in Drupal 10\/11?"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Do you know how important writing test cases for your functionalities you are developing is? Drupal always supports and gives an easy way to write test cases because it&#8217;s a vital part of development. It always helps in to early detection of bugs before deployment and also helps us to understand the code structure in a better way and the understanding of the business use cases.<\/p>\n<p>In this article, we will understand How Efficiently we can run a test case using the<strong> PHPUnit framework in Drupal 10 and Drupal 11<\/strong>. Also, we will explore all the steps starting with installing the PHPUnit module via Composer, then we will understand what the necessary changes are in its configuration file, where we should locate this file, and then we will check a working example of writing a test case and how many ways we can run our test case.<\/p>\n<div id=\"attachment_71484\" style=\"width: 831px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-71484\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-71484\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/04\/unittest-vase-in-drupal-300x200.webp\" alt=\"unittest case in drupal\" width=\"821\" height=\"548\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/04\/unittest-vase-in-drupal-300x200.webp 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/04\/unittest-vase-in-drupal-1024x683.webp 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/04\/unittest-vase-in-drupal-768x512.webp 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/04\/unittest-vase-in-drupal-624x416.webp 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/04\/unittest-vase-in-drupal.webp 1536w\" sizes=\"(max-width: 821px) 100vw, 821px\" \/><\/a><p id=\"caption-attachment-71484\" class=\"wp-caption-text\">PHPUnit in Drupal<\/p><\/div>\n<h2>Here is the step-by-step process to write and run PHPUnit test cases in Drupal:<\/h2>\n<h3>Step 1: Install PHPUnit in Drupal<\/h3>\n<p>Install the PHPUnit module via below command:<\/p>\n<pre><strong>composer require --dev phpunit\/phpunit<\/strong><\/pre>\n<p>Executing this command adds PHPUnit to your project\u2019s development dependencies.<\/p>\n<h3><strong>Step 2:Configure the phpunit.xml file<\/strong><\/h3>\n<p>PHPUnit.xml.dist is the default PHPUnit configuration file included with Drupal core. Just copy this file to the root directory of your Drupal project and rename it phpunit.xml to begin using it. The command for this is as follows:<\/p>\n<p>If your Drupal 10\/11 doesn&#8217;t have a web folder, then run below command:<\/p>\n<pre><strong>cp core\/phpunit.xml.dist phpunit.xml<\/strong><\/pre>\n<p>If your Drupal 10\/11 have a web folder, then run below command:<\/p>\n<pre><strong>cp web\/core\/phpunit.xml.dist web\/phpunit.xml<\/strong><\/pre>\n<p>In this configuration file, you can define test suite directories, test files, bootstrap files, environment variables, and other settings.<\/p>\n<p>Update this PHPUnit configuration file as per your requirement:<\/p>\n<p>Add the module test class path as an example below. Here we have 2 types of test cases written, Unit Test cases and Functional Test cases. For each test type, you have to mention each module test class path.<\/p>\n<p>Here, we have both <strong>Unit<\/strong> and <strong>Functional<\/strong> <strong>Test Cases<\/strong>. This can be any sort of test type based on your requirements. You need to mention the correct test class path, and mention under test type tags separately<\/p>\n<p><strong>NOTE:<\/strong> In the phpunit.xml file, under the &lt;testsuites&gt; tag, please remove unused test suites for any other tests, like functional\/Kernel or any other if they have not currently been written, because if you keep those configurations, it will give you errors on running the whole test suite.<\/p>\n<pre><strong>&lt;phpunit bootstrap=\"core\/tests\/bootstrap.php\"<\/strong>\r\n<strong>         colors=\"true\"<\/strong>\r\n<strong>         verbose=\"true\"<\/strong>\r\n<strong>         stopOnFailure=\"false\"&gt;<\/strong>\r\n<strong>   &lt;testsuites&gt;\r\n      &lt;testsuite name=\"Custom Unit Tests\"&gt;\r\n        &lt;directory&gt;modules\/custom\/myexample_grlogger\/tests\/src\/Unit&lt;\/directory&gt;\r\n        &lt;directory&gt;modules\/custom\/string_matcher\/tests\/src\/Unit&lt;\/directory&gt;\r\n   &lt;\/testsuite&gt;\r\n   &lt;testsuite name=\"Functional Unit Tests\"&gt;\r\n        &lt;directory&gt;modules\/custom\/myexample_grlogger\/tests\/src\/Functional&lt;\/directory&gt;\r\n        &lt;directory&gt;modules\/custom\/string_matcher\/tests\/src\/Functional&lt;\/directory&gt;\r\n   &lt;\/testsuite&gt;\r\n&lt;\/testsuites&gt;<\/strong>\r\n<strong>&lt;\/phpunit&gt;<\/strong><\/pre>\n<p>Also, confirm if the core\/tests\/bootstrap.php file exists, and the path of the bootstrap file must be as below :<\/p>\n<pre>&lt;phpunit bootstrap=\"core\/tests\/bootstrap.php\" \r\n ...\r\n&lt;testsuites&gt;\r\n...<\/pre>\n<p>This will be the same in Drupal 10 and Drupal 11 and whether your project folder contains a web folder for core and other files, or there is no web folder in your Drupal project folder.<\/p>\n<h3>Step 3: Example: Create a custom module to write and run the test case:<\/h3>\n<p>We have taken an example of a use case where we need to enforce business rules for a service that compares two strings and logs messages in Drupal, creating a maintainable solution with PHPUnit-tested services, configurable discount tiers, and audit logging capabilities.<\/p>\n<p>To\u00a0help\u00a0you\u00a0understand\u00a0the\u00a0folder\u00a0structure,\u00a0here\u00a0is\u00a0an\u00a0example\u00a0of\u00a0a\u00a0file\u00a0and\u00a0folder\u00a0structure.<\/p>\n<div id=\"attachment_73555\" style=\"width: 635px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-73555\" decoding=\"async\" loading=\"lazy\" class=\"size-large wp-image-73555\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/07\/drupal-custom-module-folders-829x1024.png\" alt=\"PHPUnit in Drupal\" width=\"625\" height=\"772\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/07\/drupal-custom-module-folders-829x1024.png 829w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/drupal-custom-module-folders-243x300.png 243w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/drupal-custom-module-folders-768x949.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/drupal-custom-module-folders-624x771.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/drupal-custom-module-folders.png 1182w\" sizes=\"(max-width: 625px) 100vw, 625px\" \/><p id=\"caption-attachment-73555\" class=\"wp-caption-text\">Folder Structure<\/p><\/div>\n<h3><strong>Step 4: Construct a custom Service<\/strong><\/h3>\n<p>This service compares two strings and logs messages in Drupal.<\/p>\n<p><strong>File path: modules\/custom\/string_matcher\/src\/Service\/StringComparerService.php<\/strong><\/p>\n<pre>namespace Drupal\\string_matcher\\Service;\r\n\r\nuse Psr\\Log\\LoggerInterface;\r\nuse Drupal\\Core\\Logger\\LoggerChannelFactoryInterface;\r\n\r\nclass StringComparerService {\r\n\r\n  protected LoggerInterface $logger;\r\n  public function __construct(LoggerChannelFactoryInterface $logger_factory) {\r\n    $this-&gt;logger = $logger_factory-&gt;get('string_matcher');\r\n  }\r\n\r\n \/**\r\n  * Compares two strings and logs the result.\r\n  *\/\r\n  public function isEqual(string $a, string $b): bool {\r\n    $result = $a === $b;\r\n    $this-&gt;logger-&gt;info('Comparing strings: \"@a\" and \"@b\" =&gt; @result', [\r\n      '@a' =&gt; $a,\r\n      '@b' =&gt; $b,\r\n      '@result' =&gt; $result ? 'Match' : 'No Match',\r\n    ]);\r\n  return $result;\r\n  }\r\n}<\/pre>\n<h3><strong>Step 5: Add the Service by *.service.yml config file<\/strong><\/h3>\n<p><strong>File: modules\/custom\/string_matcher\/string_matcher.services.yml<\/strong><\/p>\n<pre>services:\r\n  string_matcher.comparer:\r\n    class: Drupal\\string_matcher\\StringComparerService\r\n    arguments: ['@logger.factory']<\/pre>\n<h3><strong>Step 6: Write a Unit test for the Drupal Service StringComparerService service<\/strong><\/h3>\n<p>Writing the test for the custom service method is the last step. We\u2019ll mock dependencies (if any) and test edge cases.<\/p>\n<p><strong>File: modules\/custom\/string_matcher\/tests\/src\/Unit\/StringComparerServiceTest.php<\/strong><\/p>\n<pre>namespace Drupal\\Tests\\string_matcher\\Unit;\r\n\r\nuse Drupal\\Core\\Logger\\LoggerChannelFactoryInterface;\r\nuse Drupal\\Core\\Logger\\LoggerChannelInterface;\r\nuse Drupal\\string_matcher\\Service\\StringComparerService;\r\nuse PHPUnit\\Framework\\TestCase;\r\n\r\nclass StringComparerServiceTest extends TestCase {\r\n\r\nprotected $loggerFactoryMock;\r\nprotected $loggerMock;\r\nprotected $service;\r\n\r\nprotected function setUp(): void {\r\n  parent::setUp();\r\n  $this-&gt;loggerMock = $this-&gt;createMock(LoggerChannelInterface::class);\r\n  $this-&gt;loggerMock-&gt;expects($this-&gt;any())\r\n   -&gt;method('info');\r\n\r\n  $this-&gt;loggerFactoryMock = $this-&gt;createMock(LoggerChannelFactoryInterface::class);\r\n  $this-&gt;loggerFactoryMock-&gt;expects($this-&gt;any())\r\n    -&gt;method('get')\r\n    -&gt;with('string_matcher')\r\n    -&gt;willReturn($this-&gt;loggerMock);\r\n\r\n  $this-&gt;service = new StringComparerService($this-&gt;loggerFactoryMock);\r\n }\r\n\r\n public function testStringsAreEqual() {\r\n  $this-&gt;assertTrue($this-&gt;service-&gt;isEqual('hello', 'hello'));\r\n }\r\n\r\n public function testStringsAreNotEqual() {\r\n  $this-&gt;assertFalse($this-&gt;service-&gt;isEqual('hello', 'world'));\r\n }\r\n}<\/pre>\n<h3>Step 7: Run the\u00a0 Testcases using PHPUnit in Drupal 10\/11<\/h3>\n<p>Run all commands below in the Drupal project root directory. If there is a web folder (maybe in Drupal 11) then run these commands outside web folder.<\/p>\n<ol>\n<li><strong>Running the Test Suite<\/strong><\/li>\n<\/ol>\n<p>In Drupal 10, initiate the test runner with this terminal command<\/p>\n<pre>Here, in Drupal 10, we have used php8.2 and PHPUnit 9.5\r\n.\/vendor\/bin\/phpunit<\/pre>\n<p>For Drupal 11, initiate the test runner with this terminal command<\/p>\n<pre>In Drupal 11, we are using php8.3,  PHPUnit 10.5+, for this we can run the above command in either ways below:\r\n\r\nCommand1 =&gt;     .\/vendor\/bin\/phpunit --configuration web\/phpunit.xml\r\n \r\nCommand2  =&gt;    .\/vendor\/bin\/phpunit -c web\/phpunit.xml<\/pre>\n<p>Without requiring you to specify specific test files, this will automatically find and run every test file you have indicated in your setup.<\/p>\n<p>This command&#8217;s result is displayed below:<\/p>\n<div id=\"attachment_73566\" style=\"width: 635px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-73566\" decoding=\"async\" loading=\"lazy\" class=\"size-large wp-image-73566\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-testsuite-1024x225.png\" alt=\"PHPUnit in Drupal\" width=\"625\" height=\"137\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-testsuite-1024x225.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-testsuite-300x66.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-testsuite-768x169.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-testsuite-624x137.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-testsuite.png 1218w\" sizes=\"(max-width: 625px) 100vw, 625px\" \/><p id=\"caption-attachment-73566\" class=\"wp-caption-text\">Run Test suite<\/p><\/div>\n<p>2. <strong>Running a Specific Test File<\/strong><\/p>\n<p>Use\u00a0this\u00a0command\u00a0to\u00a0run\u00a0tests\u00a0from\u00a0a\u00a0specific\u00a0file\u00a0(such\u00a0as\u00a0one\u00a0that\u00a0has\u00a0several\u00a0test\u00a0cases\u00a0pertaining\u00a0to\u00a0a\u00a0single\u00a0service\u00a0or\u00a0class):<\/p>\n<pre><strong>In Drupal 10, run below command for single file:<\/strong>\r\n\r\n.\/vendor\/bin\/phpunit modules\/custom\/string_matcher\/tests\/src\/Unit\/StringComparerServiceTest.php<\/pre>\n<pre><strong>In Drupal 11, run below command for single file:<\/strong>\r\n\r\n.\/vendor\/bin\/phpunit -c web\/phpunit.xml web\/modules\/custom\/string_matcher\/tests\/src\/Unit\/StringComparerServiceTest.php<\/pre>\n<p>The above command will run a single test file for all test cases. For this example, we have 2 test cases that will run by below command.<\/p>\n<p>The outcome of this command is shown below:<\/p>\n<div id=\"attachment_73567\" style=\"width: 944px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-73567\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-73567 \" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-1024x133.png\" alt=\"PHPUnit in Drupal\" width=\"934\" height=\"121\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-1024x133.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-300x39.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-768x100.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-1536x200.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-2048x267.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-file-624x81.png 624w\" sizes=\"(max-width: 934px) 100vw, 934px\" \/><p id=\"caption-attachment-73567\" class=\"wp-caption-text\">Run by PHPUnit Single File<\/p><\/div>\n<p>3. <strong>Running a Specific Test Method<\/strong><\/p>\n<p>If you need to run just a single method from a test class, you can make use of PHPUnit\u2019s &#8211;filter option to do so.<\/p>\n<pre>In Drupal 10, use below command to run single function in a test file:\r\n\r\n.\/vendor\/bin\/phpunit --filter testStringsAreEqual<\/pre>\n<pre>In Drupal 11, use below command to run single function in a test file:\r\n\r\n.\/vendor\/bin\/phpunit -c web\/phpunit.xml --filter StringComparerServiceTest::testStringsAreEqual<\/pre>\n<p>This command will run a single testcase, and this helps save time when troubleshooting.<\/p>\n<p>The outcome of this command is shown below:<\/p>\n<div id=\"attachment_73568\" style=\"width: 965px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-73568\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-73568\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function-1024x153.png\" alt=\"PHPUnit in Drupal\" width=\"955\" height=\"143\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function-1024x153.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function-300x45.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function-768x115.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function-1536x229.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function-624x93.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/run-by-phpunit-single-function.png 1850w\" sizes=\"(max-width: 955px) 100vw, 955px\" \/><p id=\"caption-attachment-73568\" class=\"wp-caption-text\">Run by PHPUnit Single Function<\/p><\/div>\n<h3>Conclusion<\/h3>\n<p>Test Cases help in future code refactoring, new feature development, changes to existing features, early bug detection, faster development, and many more. A successful writing Unit Test in Drupal CMS primarily depends on how effectively you apply best practices and how much of your code is reusable, and how many scenarios are handled. This method of writing code allows you to create better tests with less work and achieve excellent outcomes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Do you know how important writing test cases for your functionalities you are developing is? Drupal always supports and gives an easy way to write test cases because it&#8217;s a vital part of development. It always helps in to early detection of bugs before deployment and also helps us to understand the code structure [&hellip;]<\/p>\n","protected":false},"author":1508,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":231},"categories":[3602],"tags":[4862,5400,6421,7247],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/71481"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1508"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=71481"}],"version-history":[{"count":77,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/71481\/revisions"}],"predecessor-version":[{"id":73740,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/71481\/revisions\/73740"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=71481"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=71481"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=71481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}