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="EliasHaeussler\PHPUnitAttributes\PHPUnitAttributesExtension" />
+ </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="EliasHaeussler\PHPUnitAttributes\PHPUnitAttributesExtension" />
+ <bootstrap class="EliasHaeussler\PHPUnitAttributes\PHPUnitAttributesExtension">
+ <parameter name="fancyParameterName" value="fancyParameterValue" />
+ </bootstrap>
</extensions>
The following attributes are shipped with this library:
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="EliasHaeussler\PHPUnitAttributes\PHPUnitAttributesExtension">
<parameter name="handleMissingClasses" value="fail" />
</bootstrap>
</extensions>
final class DummyTest extends TestCase
{
#[RequiresClass(AnImportantClass::class)]
public function testDummyAction(): void
{
// ...
}
}
More examples
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.
}
}
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="EliasHaeussler\PHPUnitAttributes\PHPUnitAttributesExtension">
<parameter name="handleUnsatisfiedPackageRequirements" value="fail" />
</bootstrap>
</extensions>
final class DummyTest extends TestCase
{
#[RequiresPackage('symfony/console')]
public function testDummyAction(): void
{
// ...
}
}
More examples
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).