zenstruck / console-test
Alternative, opinionated helper for testing Symfony console commands.
Fund package maintenance!
kbond
Installs: 217 717
Dependents: 18
Suggesters: 0
Security: 0
Stars: 43
Watchers: 2
Forks: 4
Open Issues: 1
Requires
- php: >=8.0
- symfony/console: ^5.4|^6.0|^7.0
- zenstruck/assert: ^1.0
Requires (Dev)
- phpstan/phpstan: ^1.4
- phpunit/phpunit: ^9.5.0
- symfony/framework-bundle: ^5.4|^6.0|^7.0
- symfony/phpunit-bridge: ^6.2|^7.0
README
Alternative, opinionated helper for testing Symfony console commands. This package is an alternative to
Symfony\Component\Console\Tester\CommandTester
and helps make your tests more expressive and concise.
Installation
composer require --dev zenstruck/console-test
Symfony Framework Usage
You can run console commands in your tests by using the InteractsWithConsole
trait in your
KernelTestCase
/WebTestCase
tests:
use App\Command\CreateUserCommand; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Console\Test\InteractsWithConsole; class CreateUserCommandTest extends KernelTestCase { use InteractsWithConsole; public function test_can_create_user(): void { $this->executeConsoleCommand('create:user kbond --admin --role=ROLE_EMPLOYEE --role=ROLE_MANAGER') ->assertSuccessful() // command exit code is 0 ->assertOutputContains('Creating admin user "kbond"') ->assertOutputContains('with roles: ROLE_EMPLOYEE, ROLE_MANAGER') ->assertOutputNotContains('regular user') ; // advanced usage $this->consoleCommand(CreateUserCommand::class) // can use the command class or "name" ->splitOutputStreams() // by default stdout/stderr are combined, this options splits them ->addArgument('kbond') ->addOption('--admin') // with or without "--" prefix ->addOption('role', ['ROLE_EMPLOYEE', 'ROLE_MANAGER']) ->addOption('-R') // shortcut options require the "-" prefix ->addOption('-vv') // by default, output has normal verbosity, use the standard options to change (-q, -v, -vv, -vvv) ->addOption('--ansi') // by default, output is undecorated, use this option to decorate ->execute() // run the command ->assertSuccessful() ->assertStatusCode(0) // equivalent to ->assertSuccessful() ->assertOutputContains('Creating admin user "kbond"') ->assertErrorOutputContains('this is in stderr') // used in conjunction with ->splitOutputStreams() ->assertErrorOutputNotContains('admin user') // used in conjunction with ->splitOutputStreams() ->dump() // dump() the status code/outputs and continue ->dd() // dd() the status code/outputs ; // testing interactive commands $this->executeConsoleCommand('create:user', ['kbond']) ->assertSuccessful() ->assertOutputContains('Creating regular user "kbond"') ; // advanced testing interactive commands $this->consoleCommand(CreateUserCommand::class) ->addInput('kbond') ->addOption('--no-interaction') // commands are run interactively if input is provided, use this option to disable ->execute() ->assertSuccessful() ->assertOutputContains('Creating regular user "kbond"') ; // test command throws exception $this->consoleCommand(CreateUserCommand::class) ->expectException(\RuntimeException::class, 'Username required!') ->assertStatusCode(1) // equivalent to ->assertFaulty() ->assertOutputContains('Could not create user!') // can still make assertions on output before exception was thrown ; // test completion $this->consoleCommand('create:user') ->complete('') ->is(['kevin', 'john', 'jane']) ->contains('kevin') // chain assertions ->back() // fluently go back to the TestCommand ->complete('kevin --role=')->is(['ROLE_EMPLOYEE', 'ROLE_MANAGER']) ; // access result $result = $this->executeConsoleCommand('create:user'); $result->statusCode(); $result->output(); $result->errorOutput(); } }
Standalone Usage
You can test commands in unit tests or in a non-Symfony Framework context:
use App\Command\CreateUserCommand; use PHPUnit\Framework\TestCase; use Zenstruck\Console\Test\TestCommand; class CreateUserCommandTest extends TestCase { public function test_can_create_user(): void { TestCommand::for(new CreateUserCommand(/** args... */)) ->execute('kbond --admin --role=ROLE_EMPLOYEE --role=ROLE_MANAGER') ->assertSuccessful() // command exit code is 0 ->assertOutputContains('Creating admin user "kbond"') ->assertOutputContains('with roles: ROLE_EMPLOYEE, ROLE_MANAGER') ->assertOutputNotContains('regular user') ; // advanced usage TestCommand::for(new CreateUserCommand(/** args... */)) ->splitOutputStreams() // by default stdout/stderr are combined, this options splits them ->addArgument('kbond') ->addOption('--admin') // with or without "--" prefix ->addOption('role', ['ROLE_EMPLOYEE', 'ROLE_MANAGER']) ->addOption('-R') // shortcut options require the "-" prefix ->addOption('-vv') // by default, output has normal verbosity, use the standard options to change (-q, -v, -vv, -vvv) ->addOption('--ansi') // by default, output is undecorated, use this option to decorate ->execute() ->assertSuccessful() ->assertStatusCode(0) // equivalent to ->assertSuccessful() ->assertOutputContains('Creating admin user "kbond"') ->assertErrorOutputContains('this is in stderr') // used in conjunction with ->splitOutputStreams() ->assertErrorOutputNotContains('admin user') // used in conjunction with ->splitOutputStreams() ->dump() // dump() the status code/outputs and continue ->dd() // dd() the status code/outputs ; // testing interactive commands TestCommand::for(new CreateUserCommand(/** args... */)) ->addInput('kbond') ->addOption('--no-interaction') // commands are run interactively if input is provided, use this option to disable ->execute() ->assertSuccessful() ->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 ; // test completion TestCommand::for(new CreateUserCommand(/** args... */)) ->complete('') ->is(['kevin', 'john', 'jane']) ->contains('kevin') // chain assertions ->back() // fluently go back to the TestCommand ->complete('kevin --role=')->is(['ROLE_EMPLOYEE', 'ROLE_MANAGER']) ; // access result $result = TestCommand::for(new CreateUserCommand(/** args... */))->execute(); $result->statusCode(); $result->output(); $result->errorOutput(); } }
Standardize Terminal Width
Under different terminal environments (ie Windows, Linux, Github Actions) the default
terminal width can be calculated differently. Since certain Symfony output helpers
use this to wrap long lines this can lead to output assertions failing in different
environments. It is recommended to standardize the terminal width by setting the
COLUMNS
environment variable for your test suite:
<!-- phpunit.xml --> <phpunit> <!-- ... --> <php> <env name="COLUMNS" value="120" /> </php> <!-- ... --> </phpunit>