A Composer library with additional attributes to enhance testing with PHPUnit.
composer require --dev eliashaeussler/phpunit-attributes
The library ships with a ready-to-use PHPUnit extension. It must be registered in your PHPUnit configuration file:
<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" >+ <extensions>+ <bootstrap class="EliasHaeusslerPHPUnitAttributesPHPUnitAttributesExtension" />+ </extensions> <testsuites> <testsuite name="unit"> <directory>tests</directory> </testsuite> </testsuites> <source> <include> <directory>src</directory> </include> </source> </phpunit>
Some attributes can be configured with custom extension parameters. These must be added to the extension registration section like follows:
<extensions>- <bootstrap class="EliasHaeusslerPHPUnitAttributesPHPUnitAttributesExtension" />+ <bootstrap class="EliasHaeusslerPHPUnitAttributesPHPUnitAttributesExtension">+ <parameter name="fancyParameterName" value="fancyParameterValue" />+ </bootstrap> </extensions>
The following attributes are shipped with this library:
#[RequiresClass]
#[RequiresPackage]
#[RequiresClass]
Scope: Class & Method level
With this attribute, tests or test cases can be marked as to be only executed if a certain class exists. The given class must be loadable by the current class loader (which normally is Composer's default class loader).
By default, test cases requiring non-existent classes are skipped. However, this
behavior can be configured by using the handleMissingClasses
extension parameter.
If set to fail
, test cases with missing classes will fail (defaults to skip
):
<extensions> <bootstrap class="EliasHaeusslerPHPUnitAttributesPHPUnitAttributesExtension"> <parameter name="handleMissingClasses" value="fail" /> </bootstrap> </extensions>
final class DummyTest extends TestCase { #[RequiresClass(AnImportantClass::class)]public function testDummyAction(): void{// ...} }
Class level:
#[RequiresClass(AnImportantClass::class)]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if AnImportantClass is missing.}public function testOtherDummyAction(): void{// Skipped if AnImportantClass is missing.} }
Method level:
final class DummyTest extends TestCase { #[RequiresClass(AnImportantClass::class)]public function testDummyAction(): void{// Skipped if AnImportantClass is missing.}public function testOtherDummyAction(): void{// Not skipped.} }
Class level:
#[RequiresClass(AnImportantClass::class, 'This test requires the `AnImportantClass` class.')]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if AnImportantClass is missing, along with custom message.}public function testOtherDummyAction(): void{// Skipped if AnImportantClass is missing, along with custom message.} }
Method level:
final class DummyTest extends TestCase { #[RequiresClass(AnImportantClass::class, 'This test requires the `AnImportantClass` class.')]public function testDummyAction(): void{// Skipped if AnImportantClass is missing, along with custom message.}public function testOtherDummyAction(): void{// Not skipped.} }
Class level:
#[RequiresClass(AnImportantClass::class, outcomeBehavior: OutcomeBehavior::Fail)]final class DummyTest extends TestCase {public function testDummyAction(): void{// Fails if AnImportantClass is missing.}public function testOtherDummyAction(): void{// Fails if AnImportantClass is missing.} }
Method level:
final class DummyTest extends TestCase { #[RequiresClass(AnImportantClass::class, outcomeBehavior: OutcomeBehavior::Fail)]public function testDummyAction(): void{// Fails if AnImportantClass is missing.}public function testOtherDummyAction(): void{// Does not fail.} }
Class level:
#[RequiresClass(AnImportantClass::class)] #[RequiresClass(AnotherVeryImportantClass::class)]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if AnImportantClass and/or AnotherVeryImportantClass are missing.}public function testOtherDummyAction(): void{// Skipped if AnImportantClass and/or AnotherVeryImportantClass are missing.} }
Method level:
final class DummyTest extends TestCase { #[RequiresClass(AnImportantClass::class)] #[RequiresClass(AnotherVeryImportantClass::class)]public function testDummyAction(): void{// Skipped if AnImportantClass and/or AnotherVeryImportantClass are missing.}public function testOtherDummyAction(): void{// Not skipped.} }
#[RequiresPackage]
Scope: Class & Method level
This attribute can be used to define specific package requirements for single tests as well as complete test classes. A required package is expected to be installed via Composer. You can optionally define a version constraint and a custom message.
Important
The attribute determines installed Composer packages from the build-time
generated InstalledVersions
class built by Composer. In order to properly
read from this class , it's essential to include Composer's generated
autoloader in your PHPUnit bootstrap script:
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php"><!-- ... --></phpunit>
You can also pass the script as command option: phpunit --bootstrap vendor/autoload.php
By default, test cases with unsatisfied requirements are skipped. However, this
behavior can be configured by using the handleUnsatisfiedPackageRequirements
extension parameter. If set to fail
, test cases with unsatisfied requirements
will fail (defaults to skip
):
<extensions> <bootstrap class="EliasHaeusslerPHPUnitAttributesPHPUnitAttributesExtension"> <parameter name="handleUnsatisfiedPackageRequirements" value="fail" /> </bootstrap> </extensions>
final class DummyTest extends TestCase { #[RequiresPackage('symfony/console')]public function testDummyAction(): void{// ...} }
Class level:
#[RequiresPackage('symfony/console')]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if symfony/console is not installed.}public function testOtherDummyAction(): void{// Skipped if symfony/console is not installed.} }
Method level:
final class DummyTest extends TestCase { #[RequiresPackage('symfony/console')]public function testDummyAction(): void{// Skipped if symfony/console is not installed.}public function testOtherDummyAction(): void{// Not skipped.} }
Class level:
#[RequiresPackage('symfony/*')]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if no symfony/* packages are installed.}public function testOtherDummyAction(): void{// Skipped if no symfony/* packages are installed.} }
Method level:
final class DummyTest extends TestCase { #[RequiresPackage('symfony/*')]public function testDummyAction(): void{// Skipped if no symfony/* packages are installed.}public function testOtherDummyAction(): void{// Not skipped.} }
Class level:
#[RequiresPackage('symfony/console', '>= 7')]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if installed version of symfony/console is < 7.}public function testOtherDummyAction(): void{// Skipped if installed version of symfony/console is < 7.} }
Method level:
final class DummyTest extends TestCase { #[RequiresPackage('symfony/console', '>= 7')]public function testDummyAction(): void{// Skipped if installed version of symfony/console is < 7.}public function testOtherDummyAction(): void{// Not skipped.} }
Class level:
#[RequiresPackage('symfony/console', message: 'This test requires the Symfony Console.')]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if symfony/console is not installed, along with custom message.}public function testOtherDummyAction(): void{// Skipped if symfony/console is not installed, along with custom message.} }
Method level:
final class DummyTest extends TestCase { #[RequiresPackage('symfony/console', message: 'This test requires the Symfony Console.')]public function testDummyAction(): void{// Skipped if symfony/console is not installed, along with custom message.}public function testOtherDummyAction(): void{// Not skipped.} }
Class level:
#[RequiresPackage('symfony/console', outcomeBehavior: OutcomeBehavior::Fail)]final class DummyTest extends TestCase {public function testDummyAction(): void{// Fails if symfony/console is not installed.}public function testOtherDummyAction(): void{// Fails if symfony/console is not installed.} }
Method level:
final class DummyTest extends TestCase { #[RequiresPackage('symfony/console', outcomeBehavior: OutcomeBehavior::Fail)]public function testDummyAction(): void{// Fails if symfony/console is not installed.}public function testOtherDummyAction(): void{// Does not fail.} }
Class level:
#[RequiresPackage('symfony/console')] #[RequiresPackage('guzzlehttp/guzzle')]final class DummyTest extends TestCase {public function testDummyAction(): void{// Skipped if symfony/console and/or guzzlehttp/guzzle are not installed.}public function testOtherDummyAction(): void{// Skipped if symfony/console and/or guzzlehttp/guzzle are not installed.} }
Method level:
final class DummyTest extends TestCase { #[RequiresPackage('symfony/console')] #[RequiresPackage('guzzlehttp/guzzle')]public function testDummyAction(): void{// Skipped if symfony/console and/or guzzlehttp/guzzle are not installed.}public function testOtherDummyAction(): void{// Not skipped.} }
Please have a look at CONTRIBUTING.md
.
This project is licensed under GNU General Public License 3.0 (or later).