Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Symfony/Console applications with PHPUnit

Raphael Stolt
November 08, 2023
93

Testing Symfony/Console applications with PHPUnit

Introduction to testing Symfony/Console applications with PHPUnit, presented at the Symfony Usergroup Berlin.

Raphael Stolt

November 08, 2023
Tweet

Transcript

  1. The system under test The business logic of the SUT

    ( ) is the validation, creation, and modification of the .gitattributes file of any given software 📦. http://git.io/lean- package-validator
  2. Purpose of a .gitattributes file The main* purpose of a

    .gitattributes file is to define which repository artifacts end up in a distributable package. A distributable package or Git archive is the one which is pulled into your project via package managers like Composer or pip . It helps package maintainers to reduce their 💨🌏 footprint by preventing needless file transfers.
  3. Anatomy of a .gitattributes file Maybe located in the working

    directory of a given Git repository. * text=auto eol=lf *.sqlite diff=sqlite3 .editorconfig export-ignore .gitattributes export-ignore .github/ export-ignore .gitignore export-ignore .php-cs-fixer.php export-ignore box.json.dist export-ignore CHANGELOG.md export-ignore example/ export-ignore LICENSE.md export-ignore phpstan.neon.dist export-ignore phpunit.xml.dist export-ignore README md export ignore
  4. Scenarios to test The produced exit code of the tool

    under test is very important, as it's intended to be hooked into CI pipelines via . Over time additional features emerged like showing the diff between the expected and actual .gitattributes content; or the injection of glob pattern to utilise it for packages outside of the 🐘 ecosystem. GitHub Actions
  5. Classic PHPUnit test setup $ cat phpunit.xml.dist <?xml version="1.0" encoding="UTF-8"?>

    <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="true" xsi:noNamespaceSchemaLocation= "https://schema.phpunit.de/10.3/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false"> <coverage/> <testsuites> <testsuite name="Lean Package Validator"> <directory>tests/</directory>
  6. Classic PHPUnit test setup $ tree src/ tests/ src ├──

    Analyser.php ├── Archive │ └── Validator.php ├── Archive.php ├── Commands │ ├── InitCommand.php │ └── ValidateCommand.php ├── Exceptions │ ├── GitArchiveNotValidatedYet.php │ ├── GitattributesCreationFailed.php │ ├── GitHeadNotAvailable.php │ ├── GitNotAvailable.php │ ├── InvalidGlobPatternFile.php │ ├ InvalidGlobPattern php
  7. Ways to approach testing a CLI 1. Trust your business

    logic tests and neglect the CLI input port to it 🚷
  8. Ways to approach testing a CLI 2. Utilising PHP's good

    ol' exec ⛔ #[Test] public function executableIsAvailable(): void { $command = 'php bin/lean-package-validator'; \exec($command, $output, $returnValue); $this->assertStringStartsWith( 'Lean package validator', $output[1], 'Expected application name not present.' );
  9. Ways to approach testing a CLI 3. Utilising the Symfony/Process

    component ⛔ #[Test] public function executableIsAvailable(): void { $command = 'php bin/lean-package-validator'; $process = Process::formShellCommandline($command); $process->run(); $this->assertStringStartsWith( 'Lean package validator', $process->getOutput(), 'Expected application name not present.' );
  10. Ways to approach testing a Symfony/Console based CLI 4. Utilising

    Symfony/Console's CommandTester or ApplicationTester ✅ $ tree vendor/symfony/console/Tester vendor/symfony/console/Tester ├── ApplicationTester.php ├── CommandCompletionTester.php ├── CommandTester.php ├── Constraint │ └── CommandIsSuccessful.php └── TesterTrait.php 1 directory, 5 files
  11. Ways to approach testing a Symfony/Console based CLI 4. Utilising

    Symfony/Console's CommandTester or ApplicationTester ✅ $ php ListCommandTesterPublicApi.php __construct(Command $command) execute(array $input, array $options = [])): int getDisplay(bool $normalize = false): string getErrorOutput(bool $normalize = false): string) getInput(): InputInterface getOutput(): OutputInterface getStatusCode(): int assertCommandIsSuccessful(): void setInputs(array $inputs): static
  12. InitCommandTest /** * Set up test environment. */ protected function

    setUp(): void { $this->setUpTemporaryDirectory(); if (!\defined('WORKING_DIRECTORY')) { \define('WORKING_DIRECTORY', $this->temporaryDirectory); } $this->application = $this->getApplication(); } #[Test] public function verboseOutputIsAvailableWhenDesired(): void
  13. ValidateCommandTest #[Test] public function gitattributesFileWithNoExportIgnoresShowsExpected { $analyserMock = Mockery::mock( 'Stolt\LeanPackage\Analyser[getGlobalGitignorePatterns]'

    ); $analyserMock->shouldReceive('getGlobalGitignorePatterns') ->once() ->withAnyArgs() ->andReturn([]); $expectedDisplay = " The present .gitattributes file is considered invalid. Would expect the following .gitattributes file content: # Auto detect text files and perform LF normalization
  14. Executing the tests The lean-package-validator package has a dedicated Composer

    script named lpv:test . $ composer lpv:test > phpunit PHPUnit 10.3.5 by Sebastian Bergmann and contributors. Runtime: PHP 8.2.12 Configuration: /user/stolt/work/oss/lean- 📦-validator/phpunit.xml ..................................................... 63 / 121 ................................................. 121 / 121 Time: 00:00.981, Memory: 14.00 MB OK (121 tests, 204 assertions)