Skip to content

Commit

Permalink
[feature] add TestCommand::expectException()
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Apr 1, 2022
1 parent dad0fae commit 6a26847
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 1 deletion.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ class CreateUserCommandTest extends KernelTestCase
->assertOutputContains('Creating regular user "kbond"')
;

// test command throws exception
$this->consoleCommand(CreateUserCommand::class)
->expectException(\RuntimeException::class, 'Username required!')
->assertStatusCode(1)
->assertOutputContains('Could not create user!') // can still make assertions on output before exception was thrown
;

// access result
$result = $this->executeConsoleCommand('create:user');

Expand Down Expand Up @@ -130,6 +137,13 @@ class CreateUserCommandTest extends TestCase
->assertOutputContains('Creating regular user "kbond"')
;

// test command throws exception
TestCommand::for(new CreateUserCommand(/** args... */))
->expectException(\RuntimeException::class, 'Username required!')
->assertStatusCode(1)
->assertOutputContains('Could not create user!') // can still make assertions on output before exception was thrown
;

// access result
$result = TestCommand::for(new CreateUserCommand(/** args... */))->execute();

Expand Down
39 changes: 38 additions & 1 deletion src/TestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Zenstruck\Assert;

/**
* @author Kevin Bond <kevinbond@gmail.com>
Expand All @@ -15,6 +16,10 @@ final class TestCommand

/** @var string[] */
private array $inputs = [];

/** @var callable|class-string|null */
private $expectedException;
private ?string $expectedExceptionMessage = null;
private bool $splitOutputStreams = false;

private function __construct(Command $command, string $cli)
Expand Down Expand Up @@ -96,6 +101,25 @@ public function withInput(array $inputs): self
return $this;
}

/**
* Expect executing the command will throw this exception. Fails if not thrown.
*
* @param class-string|callable $expectedException string: class name of the expected exception
* callable: uses the first argument's type-hint
* to determine the expected exception class. When
* exception is caught, callable is invoked with
* the caught exception
* @param string|null $expectedMessage Assert the caught exception message "contains"
* this string
*/
public function expectException($expectedException, ?string $expectedMessage = null): self
{
$this->expectedException = $expectedException;
$this->expectedExceptionMessage = $expectedMessage;

return $this;
}

public function execute(?string $cli = null): CommandResult
{
$autoExit = $this->application->isAutoExitEnabled();
Expand All @@ -105,7 +129,7 @@ public function execute(?string $cli = null): CommandResult
$this->application->setAutoExit(false);
$this->application->setCatchExceptions(false);

$status = $this->application->run(
$status = $this->doRun(
$input = new TestInput($cli, $this->inputs),
$output = new TestOutput($this->splitOutputStreams, $input)
);
Expand All @@ -115,4 +139,17 @@ public function execute(?string $cli = null): CommandResult

return new CommandResult($cli, $status, $output);
}

private function doRun(TestInput $input, TestOutput $output): int
{
$fn = fn() => $this->application->run($input, $output);

if (!$this->expectedException) {
return $fn();
}

Assert::that($fn)->throws($this->expectedException, $this->expectedExceptionMessage);

return 1;
}
}
54 changes: 54 additions & 0 deletions tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Zenstruck\Console\Test\Tests;

use PHPUnit\Framework\AssertionFailedError;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Zenstruck\Assert;
use Zenstruck\Console\Test\InteractsWithConsole;
use Zenstruck\Console\Test\Tests\Fixture\FixtureCommand;

Expand Down Expand Up @@ -136,6 +138,58 @@ public function exceptions_from_commands_are_thrown(): void
$this->consoleCommand('fixture:command --throw')->execute();
}

/**
* @test
*/
public function can_expect_exception(): void
{
$this->consoleCommand('fixture:command --throw')
->expectException(\RuntimeException::class)
->execute()
->assertStatusCode(1)
->assertOutputContains('Executing command...')
->assertOutputContains('Error output.')
;

$this->consoleCommand('fixture:command --throw')
->expectException(\RuntimeException::class, 'Exception thrown!')
->execute()
->assertStatusCode(1)
->assertOutputContains('Executing command...')
->assertOutputContains('Error output.')
;

$this->consoleCommand('fixture:command --throw')
->expectException(function(\RuntimeException $e) {
$this->assertSame('Exception thrown!', $e->getMessage());
})
->execute()
->assertStatusCode(1)
->assertOutputContains('Executing command...')
->assertOutputContains('Error output.')
;
}

/**
* @test
*/
public function if_expected_exception_not_thrown_fail(): void
{
Assert::that(function() {
$this->consoleCommand('fixture:command')
->expectException(\RuntimeException::class)
->execute()
;
})->throws(AssertionFailedError::class, 'No exception thrown. Expected "RuntimeException".');

Assert::that(function() {
$this->consoleCommand('fixture:command --throw')
->expectException(\LogicException::class)
->execute()
;
})->throws(AssertionFailedError::class, 'Expected "LogicException" to be thrown but got "RuntimeException".');
}

/**
* @test
*/
Expand Down

0 comments on commit 6a26847

Please sign in to comment.