{"id":223,"date":"2024-03-01T20:43:00","date_gmt":"2024-03-01T20:43:00","guid":{"rendered":"https:\/\/www.mitango.app\/?p=223"},"modified":"2025-01-07T22:33:14","modified_gmt":"2025-01-07T22:33:14","slug":"setup-integration-tests-for-your-wordpress-plugin","status":"publish","type":"post","link":"https:\/\/www.mitango.app\/fr\/2024\/03\/01\/setup-integration-tests-for-your-wordpress-plugin\/","title":{"rendered":"Setup integration tests for your WordPress Plugin"},"content":{"rendered":"<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#7835\">A vision shift<\/a><\/li><li><a href=\"#bd7b\">Unit tests vs Integration tests<\/a><\/li><li><a href=\"#c2c7\">Configure the testing environment<\/a><ul><li><a href=\"#c5c4\">wordpress\/env<\/a><\/li><li><a href=\"#3db5\">wp-media\/phpunit<\/a><\/li><li><a href=\"#c5f1\">wp-launchpad\/phpunit-wp-hooks<\/a><\/li><li><a href=\"#ba5b\">Make the base<\/a><\/li><li><a href=\"#1d8f\">Use fixtures<\/a><\/li><\/ul><\/li><li><a href=\"#2842\">Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<p id=\"7444\">A while ago I created&nbsp;<a href=\"https:\/\/www.mitango.app\/fr\/2022\/05\/01\/create-unit-tests-for-wordpress-plugin\/\" data-type=\"post\" data-id=\"161\">a first article about unit tests<\/a>&nbsp;nearly 2 years ago promising for a next article on integration tests.<\/p>\n\n\n\n<p id=\"9114\">It took a while and my vision changed a lot about tests during that period of time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"7835\">A vision shift<\/h2>\n\n\n\n<p id=\"ec27\">While writing the article on unit tests I was convinced unit tests where the first to learn to write. However, the fragility from theses tests made me change my mind as they weren\u2019t giving enough results for new developers to convince them to keep using them on the long term.<\/p>\n\n\n\n<p id=\"e1ec\">This is why I slowly changed my mind and finally started recommending to developers to begin by focusing on the most stable tests, integration tests, and that even if they are more complex that unit tests to start with.<\/p>\n\n\n\n<p id=\"f73d\">All of this is what pushed me into writing this article to teach the base of integration tests to developers wanting start testing as creating the environment to test is often the most complex part.<\/p>\n\n\n\n<p id=\"28ab\">But first to understand well what we will be doing it is important to get the main differences between unit and integration tests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"bd7b\">Unit tests vs Integration tests<\/h2>\n\n\n\n<p id=\"d0ec\">Where unit tests are supposed to test the classes or methods individually as their name let it guess, on the other side integration tests will be on an higher level testing at the level from the components or features.<\/p>\n\n\n\n<p id=\"950b\">Being at features level an advantage as now it is possible to use business assertions to test our code and it is not any longer up to us the developer to find cases from our tests.<\/p>\n\n\n\n<p id=\"c612\">At the same time testing an higher level also means higher abstraction leading into more flexibility to change and less fragile tests.<\/p>\n\n\n\n<p id=\"e8b8\">Theses two points makes theses tests a strong candidate to start with and stick to on the long term.<\/p>\n\n\n\n<p id=\"1de2\">Now that know what are integration tests and why they are the best choice to start with it is time to install the environment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"c2c7\">Configure the testing environment<\/h2>\n\n\n\n<p id=\"a1d3\">To not repeat the process I will consider that you already have a composer project initialized.<\/p>\n\n\n\n<p id=\"41c2\">If it is not the case you can follow the steps detailed in&nbsp;<a href=\"https:\/\/www.mitango.app\/fr\/2022\/05\/01\/create-unit-tests-for-wordpress-plugin\/\" data-type=\"post\" data-id=\"161\">my article on Unit tests<\/a>.<\/p>\n\n\n\n<p id=\"feb3\">As setup a full environment for integration tests can be long and complex if done manually we will have rely on some libraries to make the job for us.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"c5c4\">wordpress\/env<\/h3>\n\n\n\n<p id=\"c396\">As setting up a developing environment is something that can be time wasting WordPress community developed an automated way to setup one.<\/p>\n\n\n\n<p id=\"01e1\">As you might guess the name from that tool is&nbsp;<strong>wordpress\/env<\/strong>&nbsp;but before using it make sure you have&nbsp;<a href=\"https:\/\/docs.docker.com\/engine\/install\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Docker installed<\/a>.<\/p>\n\n\n\n<p id=\"ccab\">Once this is done the next requirement is to have npm, the Node.js package manager, installed. If it is not the case you can find a tutorial&nbsp;<a href=\"https:\/\/docs.npmjs.com\/downloading-and-installing-node-js-and-npm\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">here<\/a>.<\/p>\n\n\n\n<p id=\"708b\">With theses requirements met then the installation can start.<\/p>\n\n\n\n<p id=\"0c36\">First a new Node.js project need to be initialized at the root from our plugin project with the following command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">npm init<\/pre>\n\n\n\n<p id=\"9339\">This should generate a new&nbsp;<strong>package.json<\/strong>&nbsp;file into the folder.<\/p>\n\n\n\n<p id=\"d6d0\">Then the next step will be to install&nbsp;<strong>wordpress\/env&nbsp;<\/strong>with the following command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">npm i wordpress\/env<\/pre>\n\n\n\n<p id=\"b087\">Once this is completed we will have to add the following content inside&nbsp;<strong>package.json<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>{<br>  \"scripts\": {<br>    \"wp-env:start\": \"wp-env start\",<br>    \"wp-env:stop\": \"wp-env stop\",<br>    \"wp-env:destroy\": \"wp-env destroy\"<br>  },<br>}<\/code><\/pre>\n\n\n\n<p id=\"c3e4\">Finally the last step is to run the environment using this command:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>npm run wp-env:start<\/code><\/pre>\n\n\n\n<p id=\"37e9\">If everything goes fine then it should give the following output:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>&gt; wp-env:start<br>&gt; wp-env start<br><br>\u26a0 Warning: could not find a .wp-env.json configuration file and could not determine if '\/var\/www\/testing-wp\/web\/app\/plugins\/my_plugin' is a WordPress installation, a plugin, or a theme.<br>WordPress development site started at http:\/\/localhost:8888<br>WordPress test site started at http:\/\/localhost:8889<br>MySQL is listening on port 32770<br>MySQL for automated testing is listening on port 32769<br><br> \u2714 Done! (in 57s 413ms)<br><br>Process finished with exit code 0<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3db5\">wp-media\/phpunit<\/h3>\n\n\n\n<p id=\"a2e4\">Once the development environment is settled the next step is to setup the tests themselves.<\/p>\n\n\n\n<p id=\"6f2b\">For that we will delegate most of the work to the library&nbsp;<a href=\"https:\/\/github.com\/wp-media\/phpunit\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>wp-media\/phpunit&nbsp;<\/strong><\/a>which gonna setup and reset the environment for us.<\/p>\n\n\n\n<p id=\"16dc\">The first to use&nbsp;<a href=\"https:\/\/github.com\/wp-media\/phpunit\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>wp-media\/phpunit<\/strong><\/a><strong>&nbsp;<\/strong>is to install the library by running the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>composer i wp-media\/phpunit --dev<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"c5f1\">wp-launchpad\/phpunit-wp-hooks<\/h3>\n\n\n\n<p id=\"0d43\">In the WordPress ecosystem integration tests mocking filters is something really common due to that it is really important to make sure that operation is the less verbose as possible.<\/p>\n\n\n\n<p id=\"0510\">The library&nbsp;<a href=\"https:\/\/github.com\/wp-launchpad\/phpunit-wp-hooks\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>wp-launchpad\/phpunit-wp-hooks<\/strong><\/a>&nbsp;is done to reduce the amount of code to interact with a filter.<\/p>\n\n\n\n<p id=\"815a\">To install that library that library you need to run the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>composer i wp-launchpad\/phpunit-wp-hooks --dev<\/code><\/pre>\n\n\n\n<p id=\"5792\">Once this is done the library is installed it is now time to create base classes for tests.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ba5b\">Make the base<\/h3>\n\n\n\n<p id=\"e06e\">The first step will be to create the namespace inside the&nbsp;<strong>composer.json<\/strong>&nbsp;file from the project by adding the following code inside:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>\"autoload-dev\": {<br>  \"psr-4\": {<br>    \"MyPlugin\\\\Tests\\\\Integration\\\\\": \"Integration\/\"<br>  }<br>},<\/code><\/pre>\n\n\n\n<p id=\"e8f4\">If it is not the case inside the project we will have to create a new folder&nbsp;<strong>tests&nbsp;<\/strong>and within that folder another one named<strong>&nbsp;Integration.<\/strong><\/p>\n\n\n\n<p id=\"2b75\">Then the next step is to create file&nbsp;<strong>init-tests.php<\/strong>&nbsp;inside the&nbsp;<strong>Integration<\/strong>&nbsp;folder. The objective from that file is to setup&nbsp;<a href=\"https:\/\/github.com\/wp-media\/phpunit\" rel=\"noreferrer noopener\" target=\"_blank\"><strong>wp-media\/phpunit<\/strong><\/a>&nbsp;library by indication the position from the testing folder:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>&lt;?php<br>\/**<br> * Initializes the wp-media\/phpunit handler, which then calls the rocket integration test suite.<br> *\/<br><br>define( 'WPMEDIA_PHPUNIT_ROOT_DIR',  dirname( __DIR__ ) . DIRECTORY_SEPARATOR );<br>define( 'WPMEDIA_PHPUNIT_ROOT_TEST_DIR', __DIR__ );<br>require_once WPMEDIA_PHPUNIT_ROOT_DIR . 'vendor\/wp-media\/phpunit\/Integration\/bootstrap.php';<br><br>define( 'WPMEDIA_IS_TESTING', true ); \/\/ Used by wp-media\/{package}.<\/code><\/pre>\n\n\n\n<p id=\"0440\">Once this is done then we need to create another file&nbsp;<strong>bootstrap.php&nbsp;<\/strong>which gonna setup initial environment for our tests:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>&lt;?php<br><br>namespace MyPlugin\\Tests\\Integration;<br><br>define( 'MY_PLUGIN_PLUGIN_ROOT', dirname( dirname( __DIR__ ) ) . DIRECTORY_SEPARATOR );<br>define( 'MY_PLUGIN_TESTS_DIR', __DIR__ );<br><br>\/\/ Manually load the plugin being tested.<\/code><\/pre>\n\n\n\n<p id=\"d1bd\">Finally PHPUnit should be configured to execute the suite.<\/p>\n\n\n\n<p id=\"babc\">For that we will have to add the following content into&nbsp;<strong>phpunit.xml.dist<\/strong>&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;<br>&lt;phpunit xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https:\/\/schema.phpunit.de\/9.3\/phpunit.xsd\" bootstrap=\"init-tests.php\" backupGlobals=\"false\" colors=\"true\" beStrictAboutCoversAnnotation=\"false\" beStrictAboutOutputDuringTests=\"true\" beStrictAboutTestsThatDoNotTestAnything=\"true\" beStrictAboutTodoAnnotatedTests=\"true\" convertErrorsToExceptions=\"true\" convertNoticesToExceptions=\"true\" convertWarningsToExceptions=\"true\" verbose=\"true\"&gt;<br>  &lt;coverage includeUncoveredFiles=\"true\"&gt;<br>    &lt;include&gt;<br>      &lt;directory suffix=\".php\"&gt;..\/..\/inc&lt;\/directory&gt;<br>    &lt;\/include&gt;<br>  &lt;\/coverage&gt;<br>  &lt;testsuites&gt;<br>    &lt;testsuite name=\"integration\"&gt;<br>      &lt;directory suffix=\".php\"&gt;inc&lt;\/directory&gt;<br>    &lt;\/testsuite&gt;<br>  &lt;\/testsuites&gt;<br>&lt;\/phpunit&gt;<\/code><\/pre>\n\n\n\n<p id=\"a59c\">Finally we will have to create a base TestCase class.<\/p>\n\n\n\n<p id=\"c6b3\">It will be used to contain logic which will be common to each of our tests.<\/p>\n\n\n\n<p id=\"3ce8\">For that we will add the following content into&nbsp;<strong>TestCase.php&nbsp;<\/strong>where&nbsp;<strong>my_prefix<\/strong>&nbsp;is your plugin prefix:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>namespace MyPlugin\\Tests\\Integration;<br><br>use WPMedia\\PHPUnit\\Integration\\TestCase as BaseTestCase;<br>use WPLaunchpadPHPUnitWPHooks\\MockHooks;<br><br>abstract class TestCase extends BaseTestCase<br>{<br>    use MockHooks;<br><br>    public function set_up() {<br>        parent::set_up();<br><br>        $this-&gt;mockHooks();<br>    }<br><br>    public function tear_down()<br>    {<br>        $this-&gt;resetHooks();<br><br>        parent::tear_down();<br>    }<br><br>    function getPrefix(): string<br>    {<br>        return 'my_prefix';<br>    }<br><br>    function getCurrentTest(): string<br>    {<br>        return $this-&gt;getName();<br>    }<br>}<\/code><\/pre>\n\n\n\n<p id=\"c0ac\">Finally the last step is to add the script to launch integration tests inside&nbsp;<strong>composer.json<\/strong>&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\"test-integration\": \"\\\"vendor\/bin\/phpunit\\\" --testsuite integration --colors=always --configuration tests\/Integration\/phpunit.xml.dist --exclude-group AdminOnly,,\",<\/pre>\n\n\n\n<p id=\"7efe\">And add the script to run the previous script inside&nbsp;<strong>package.json<\/strong>&nbsp;where&nbsp;<strong>my_plugin<\/strong>&nbsp;is the name from the directory from your plugin:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>\"integration\": \"wp-env run cli --env-cwd=wp-content\/plugins\/my_plugin composer run test-integration\",<\/code><\/pre>\n\n\n\n<p id=\"fc76\">It is now possible execute the tests by running the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>npm run integration<\/code><\/pre>\n\n\n\n<p id=\"1614\">If everything goes fine you should have the following output:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>&gt; integration<br>&gt; wp-env run cli --env-cwd=wp-content\/plugins\/my_plugin composer run test-integration<br><br>\u2139 Starting 'composer run test-integration' on the cli container. <br><br>&gt; \"vendor\/bin\/phpunit\" --testsuite integration --colors=always --configuration tests\/Integration\/phpunit.xml.dist<br>Installing...<br>Running as single site... To run multisite, use -c tests\/phpunit\/multisite.xml<br>Not running ajax tests. To execute these, use --group ajax.<br>Not running ms-files tests. To execute these, use --group ms-files.<br>Not running external-http tests. To execute these, use --group external-http.<br>PHPUnit 9.6.17 by Sebastian Bergmann and contributors.<br><br>Runtime:       PHP 8.2.15<br>Configuration: tests\/Integration\/phpunit.xml.dist<br><br>No tests executed!<br>\u2714 Ran `composer run test-integration` in 'cli'. (in 5s 632ms)<br><br>Process finished with exit code 0<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1d8f\">Use fixtures<\/h3>\n\n\n\n<p id=\"be44\">To fully understand the importance of fixtures you can check&nbsp;<a href=\"https:\/\/www.mitango.app\/fr\/2022\/05\/01\/create-unit-tests-for-wordpress-plugin\/\" data-type=\"post\" data-id=\"161\">my previous article about unit tests<\/a>&nbsp;where I already explained the advantages of using them.<\/p>\n\n\n\n<p id=\"0e1e\">In this article I will show how to make your tests compatible with fixtures and this time it is even simpler than with unit tests as&nbsp;<a href=\"https:\/\/github.com\/wp-media\/phpunit\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>wp-media\/phpunit<\/strong><\/a><strong>&nbsp;<\/strong>is handling a part of the complexity for us.<\/p>\n\n\n\n<p id=\"8d94\">The first part will be to add the&nbsp;<strong>Fixture<\/strong>&nbsp;folder inside the&nbsp;<strong>tests<\/strong>&nbsp;folder.<\/p>\n\n\n\n<p id=\"a838\">Then the second part will be to add the logic to load fixtures inside the TestCase class:<\/p>\n\n\n\n<pre class=\"wp-block-code is-style-light\"><code>namespace MyPlugin\\Tests\\Integration;<br><br>use WPMedia\\PHPUnit\\Integration\\TestCase as BaseTestCase;<br>use WPLaunchpadPHPUnitWPHooks\\MockHooks;<br><br>abstract class TestCase extends BaseTestCase<br>{<br>    use MockHooks;<br><br>    protected $config;<br><br>    public function set_up() {<br>        parent::set_up();<br><br>        if ( empty( $this-&gt;config ) ) {<br>            $this-&gt;loadTestDataConfig();<br>        }<br><br>        $this-&gt;mockHooks();<br>    }<br><br>    public function tear_down()<br>    {<br>        $this-&gt;resetHooks();<br><br>        parent::tear_down();<br>    }<br><br>    public function getPrefix(): string<br>    {<br>        return 'my_prefix';<br>    }<br><br>    public function getCurrentTest(): string<br>    {<br>        return $this-&gt;getName();<br>    }<br><br>    public function configTestData() {<br>        if ( empty( $this-&gt;config ) ) {<br>            $this-&gt;loadTestDataConfig();<br>        }<br><br>        return isset( $this-&gt;config&#91;'test_data'] )<br>            ? $this-&gt;config&#91;'test_data']<br>            : $this-&gt;config;<br>    }<br><br>    protected function loadTestDataConfig() {<br>        $obj      = new ReflectionObject( $this );<br>        $filename = $obj-&gt;getFileName();<br><br>        $this-&gt;config = $this-&gt;getTestData( dirname( $filename ), basename( $filename, '.php' ) );<br>    }<br>}<\/code><\/pre>\n\n\n\n<p id=\"d5ff\">Once this code is added then you are free to create your fixture inside the&nbsp;<strong>Fixtures<\/strong>&nbsp;folder and use them within your tests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"2842\">Conclusion<\/h2>\n\n\n\n<p id=\"6b6b\">Now that your environment for integration tests is setup it is now time to write your first integration test.<\/p>\n\n\n\n<p id=\"e0ed\">If you have no idea how to make it and you are in Portugal maybe you might be interested about&nbsp;<a href=\"https:\/\/www.mitango.app\/fr\/talk\/wordcamp-porto\/\" target=\"_blank\" data-type=\"talk\" data-id=\"26\" rel=\"noreferrer noopener\">my talk on integration testing<\/a>&nbsp;on the 18 May 2024 at the Porto WordCamp.<\/p>\n\n\n\n<p id=\"b9bf\">However, if it is not the case don\u2019t worry I plan on writing more articles on integration tests and a book on plugin development in the future.<\/p>\n\n\n\n<p><\/p>","protected":false},"excerpt":{"rendered":"<p>A while ago I created\u00a0a first article about unit tests\u00a0nearly 2 years ago promising for a next article on integration tests.<\/p>\n<p>It took a while and my vision changed a lot about tests during that period of time.<\/p>","protected":false},"author":1,"featured_media":162,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[18,26],"tags":[58,19,20,22,5,49,50],"class_list":["post-223","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress","category-testing","tag-integration-testing","tag-php","tag-plugin-development","tag-testing","tag-wordpress","tag-wordpress-development","tag-wordpress-plugins"],"jetpack_publicize_connections":[],"acf":[],"_links":{"self":[{"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/posts\/223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/comments?post=223"}],"version-history":[{"count":3,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/posts\/223\/revisions"}],"predecessor-version":[{"id":413,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/posts\/223\/revisions\/413"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/media\/162"}],"wp:attachment":[{"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/media?parent=223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/categories?post=223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mitango.app\/fr\/wp-json\/wp\/v2\/tags?post=223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}