WP-CLI is the command-line interface for WordPress to interact, manage and maintain it. Writing some custom CLI commands are necessary when we manage a large customised website or a complex plugin. It’s obvious that we need to write PHPUnit tests to maintain the stability of the product over the period of time.

Read An Introduction to unit testing WordPress plugin using PHPUnit and Unit testing custom WordPress REST API to understand and set up a plugin with PHPUnit integrated.

Setting up WP-CLI

WordPress.org recommends installing the WP-CLI using the phar files. Run below commands to install them quickly.

➜ curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

➜ chmod +x wp-cli.phar
➜ sudo mv wp-cli.phar /usr/local/bin/wp

Now try checking everything installed as expected.

➜ wp --info

OS: Darwin 19.6.0 Darwin Kernel Version 19.6.0: Thu Jun 18 20:49:00 PDT 2020; root:xnu-6153.141.1~1/RELEASE_X86_64 x86_64
Shell: /bin/zsh
PHP binary: /usr/bin/php
PHP version: 7.3.11
php.ini used:
WP-CLI root dir: phar://wp-cli.phar/vendor/wp-cli/wp-cli
WP-CLI vendor dir: phar://wp-cli.phar/vendor
WP-CLI packages dir:
WP-CLI global config:
WP-CLI project config:
WP-CLI version: 2.4.0

Custom WP-CLI command

Now let’s consider a scenario where we need to write a custom CLI command to generate a report which shows the numbers of articles published per category in a given month.

This article from firxworx.com will explain each and every piece of writing custom CLI command in detail.

Consider our custom CLI command class will look something like below.

<?php

class CLI_Monthly_Reports extends WP_CLI_Command {

	/**
	 * Year number.
	 *
	 * @var string $year
	 */
	private $year = '';

	/**
	 * Month number.
	 *
	 * @var string
	 */
	private $month = '';

	/**
	 * Columns.
	 *
	 * @var array
	 */
	private $columns = [
		'Category',
		'Count',
	];

	/**
	 * Generate monthly article reports.
	 *
	 * [--month]
	 * : for specific month
	 *
	 * @subcommand monthly-reports
	 *
	 * @param array $args       PositionaL Arguments.
	 * @param array $args_assoc Associative Arguments.
	 */
	public function generate_reports( array $args, array $args_assoc = [] ): void {
		$this->year  = gmdate( 'Y' );
		$this->month = gmdate( 'm' );

		if ( ! empty( $args_assoc['month'] ) ) {
			$this->month = $args_assoc['month'];
		}

		$categories      = [];
		$categories = get_categories( [ 'hide_empty' => false ] );
		if ( empty( $categories ) ) {
			WP_CLI::Error( 'No categories found.' );
		}

		\WP_CLI\Utils\format_items(
			'table',
			$this->format_data( $categories ),
			$this->columns
		);
	}

	/**
	 * Format categories.
	 *
	 * @param array $categories
	 *
	 * @return array
	 */
	private function format_data( array $categories ): array {
		$format = [];

		foreach ( $categories as $category ) {
			array_push(
				$format,
				[
					$this->columns[0] => $category->name,
					$this->columns[1] => $this->get_post_cout_by_category_id( $category->term_id ),
				]
			);
		}

		return $format;
	}

	/**
	 * Get Post count by category.
	 *
	 * @param int $id Category Id.
	 *
	 * @return int
	 */
	private function get_post_cout_by_category_id( int $id ): int {
		$args      = [
			'post_type'      => 'post',
			'posts_per_page' => 1,
			'year'           => esc_attr( $this->year ),
			'monthnum'       => esc_attr( $this->month ),
			'cat'            => esc_attr( $id ),
		];
		$the_query = new WP_Query( $args );

		return $the_query->found_posts;
	}
}
WP_CLI::add_command( 'd9_cli', 'CLI_Monthly_Reports' );

To get the report, run the below command.

➜ wp d9_cli monthly-reports --month=9

When we run this in a fresh WordPress installation you will get an output something like below.

+---------------+-------+
| Category      | Count |
+---------------+-------+
| Uncategorized | 1     |
+---------------+-------+

Setting up CLI context for PHPUnit

To test a WP CLI script we need to load the basic CLI classes before hand as it will not be loaded by default. Thanks to Pole Vault Web for his effort in finding the very basic requirement and sharing it.

So we need to include some of the WP CLI files in our tests bootstrap file. Add the below code at the end of your bootstrap.php file.

/**
* Bootstrap the CLI dependencies
*
* This is important to test the CLI classes.
*/
if ( ! defined( 'WP_CLI_ROOT' ) ) {
	define( 'WP_CLI_ROOT', '/path/to/vendor/wp-cli/wp-cli' );
}

include WP_CLI_ROOT . '/php/utils.php';
include WP_CLI_ROOT . '/php/dispatcher.php';
include WP_CLI_ROOT . '/php/class-wp-cli.php';
include WP_CLI_ROOT . '/php/class-wp-cli-command.php';

\WP_CLI\Utils\load_dependencies();

This can be added either in bootstrap.php file or in your test class based on your requirement and project needs.

Testing custom WP CLI command

Now it’s time to write some test for our custom CLI class and method.

<?php
/**
 * Class Test_CLI_Monthly_Reports
 */

/**
 * Tests for CLI_Monthly_Reports class
 */
class Test_CLI_Monthly_Reports extends WP_UnitTestCase {

	/**
	* Test for generate reports on current month.
	*/
	public function test_generate_report_for_current_month() {
		$monthly_report = new CLI_Monthly_Reports();

		$output = '+---------------+-------+
| Category      | Count |
+---------------+-------+
| Uncategorized | 1     |
+---------------+-------+
';
		$monthly_report->generate_reports( [] );
		$this->expectOutputString( $output );
	}

	/**
	* Test for generate reports on previous month.
	*/
	public function test_generate_report_for_previous_month() {
		$monthly_report = new CLI_Monthly_Reports();

		$output = '+---------------+-------+
| Category      | Count |
+---------------+-------+
| Uncategorized | 0     |
+---------------+-------+
';
		$monthly_report->generate_reports( [], [ 'month' => gmdate( 'm', strtotime( 'previous month' ) ) ] );
		$this->expectOutputString( $output );
	}
}

This will test the public method of CLI_Monthly_Reports class, as that’s the only method available outside the class. We should always test only the public methods of the class for good.

So running below command will test our CLI class.

➜ ./vendor/bin/phpunit tests/test-cli-monthly-reports.php

We should see the below output after the test runs.

Installing…
Running as single site… To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

..                        2 / 2 (100%)
Time: 1.37 seconds, Memory: 28.00 MB
OK (2 tests, 2 assertions)

This is a very simple example shows how we can write tests for our custom CLI commands and we can extend it with more complex logic.