Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Added a `Option` support class and `option()` helper to retrieve options in a type-safe manner.
- Added `fail()` method to commands to allow for a command to fail with a message.

### Fixed

- Fixed command testing to proper handle failed commands.

## v1.5.4 - 2025-03-06

Expand Down
23 changes: 22 additions & 1 deletion src/mantle/console/class-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

/**
* CLI Command for Service Providers
Expand Down Expand Up @@ -186,8 +187,28 @@ public function set_container( \Mantle\Contracts\Application $container ): void
}

/**
* Retrieve the application container. */
* Retrieve the application container.
*/
public function get_container(): \Mantle\Contracts\Application {
return $this->container;
}

/**
* Fail the command.
*
* @param Throwable|string|null $exception Exception to throw.
*
* @throws Manually_Failed_Exception|Throwable Thrown exception.
*/
public function fail( Throwable|string|null $exception = null ): void {
if ( is_null( $exception ) ) {
$exception = 'Command manually failed.';
}

if ( is_string( $exception ) ) {
$exception = new Manually_Failed_Exception( $exception );
}

throw $exception;
}
}
15 changes: 15 additions & 0 deletions src/mantle/console/class-manually-failed-exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Manually_Failed_Exception class file
*
* @package Mantle
*/

namespace Mantle\Console;

use RuntimeException;

/**
* Manually Failed Command Exception
*/
class Manually_Failed_Exception extends RuntimeException {}
54 changes: 39 additions & 15 deletions src/mantle/testing/class-test-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Symfony\Component\Console\Tester\CommandTester;
use PHPUnit\Framework\Assert;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Throwable;

/**
* Faux "PendingCommand" class for unit testing.
Expand All @@ -28,6 +30,11 @@ class Test_Command {
*/
protected CommandTester $tester;

/**
* Exception thrown during command execution.
*/
protected Throwable $exception;

/**
* Flag if the command has been executed.
*/
Expand Down Expand Up @@ -139,6 +146,10 @@ public function dd(): never {
$this->run();
}

if ( isset( $this->exception ) ) {
dd( $this->exception );
}

dd( $this->tester->getDisplay() );
}

Expand Down Expand Up @@ -179,7 +190,7 @@ protected function verify_command(): void {
}

/**
* Run the command.
* Run the command and verify the expectations.
*/
public function run(): static {
$this->has_executed = true;
Expand All @@ -188,8 +199,8 @@ public function run(): static {
$this->tester = $this->app->make(
\Mantle\Framework\Console\Kernel::class
)->test( $this->command, $this->arguments );
} catch ( CommandNotFoundException ) {
$this->test->fail( "Command [{$this->command}] not found." );
} catch ( Throwable $e ) {
$this->exception = $e;
}

$this->verify_expectations();
Expand All @@ -203,7 +214,7 @@ public function run(): static {
protected function verify_expectations(): void {
// Assert that the exit code matches the expected exit code.
if ( null !== $this->expected_exit_code ) {
$exit_code = $this->tester->getStatusCode();
$exit_code = $this->get_status_code();

$this->test->assertEquals(
$this->expected_exit_code,
Expand All @@ -213,29 +224,20 @@ protected function verify_expectations(): void {
}

if ( ! empty( $this->expected_output ) ) {
$output = $this->tester->getDisplay();
$output = $this->get_output();

foreach ( $this->expected_output as $expected_output ) {
$this->test->assertStringContainsString( $expected_output, $output );
}
}

if ( ! empty( $this->unexpected_output ) ) {
$output = $this->tester->getDisplay();
$output = $this->get_output();

foreach ( $this->unexpected_output as $unexpected_output ) {
$this->test->assertStringNotContainsString( $unexpected_output, $output );
}
}

// todo: add output substring assertions.
// if ( ! empty( $this->expected_output_substrings ) ) {
// $output = $this->tester->getDisplay();

// foreach ( $this->expected_output_substrings as $expected_output_substring ) {
// $this->test->assertStringContainsString( $expected_output_substring, $output );
// }
// }
}

/**
Expand All @@ -246,4 +248,26 @@ public function __destruct() {
$this->run();
}
}

/**
* Retrieve the output of the command.
*/
public function get_output(): string {
return match ( true ) {
isset( $this->exception ) => $this->exception->getMessage(),
isset( $this->tester ) => $this->tester->getDisplay(),
default => '',
};
}

/**
* Retrieve the status code of the command.
*/
public function get_status_code(): int {
return match ( true ) {
isset( $this->exception ) => Command::FAILURE,
isset( $this->tester ) => $this->tester->getStatusCode(),
default => Command::FAILURE,
};
}
}
14 changes: 14 additions & 0 deletions tests/Testing/Concerns/InteractsWithConsoleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ public function test_list_command() {
->assertOk();
}

public function test_command_failure(): void {
Console::command( 'fail', fn () => $this->fail() );

$this->command( 'wp mantle fail' )
->assertOutputContains( 'Command manually failed' )
->assertFailed();

Console::command( 'fail:message', fn () => $this->fail( 'With message' ) );

$this->command( 'wp mantle fail:message' )
->assertOutputContains( 'With message' )
->assertFailed();
}

public function test_closure_command() {
Console::command( 'hello-world', fn () => $this->info( 'Hello World' ) )
->describe( 'Command description' );
Expand Down
Loading