ciaranmcnulty/versionbasedtestskipper
A PHPUnit extension that skips tests based on the running PHP version, letting you annotate tests with version constraints so they are automatically skipped or run on specific PHP versions, keeping suites passing across environments without manual conditional logic.
ciaranmcnulty/versionbasedtestskipper is a small dev-only library that lets a test declare “skip me on this PHP version range” without rewriting the test for every entry in your CI matrix.
composer require --dev ciaranmcnulty/versionbasedtestskipper
Look at composer.json and src/ first. The package is small enough that the source is the authoritative documentation; confirm the exact namespace, trait/class name, and annotation spelling for the tag you installed before wiring it into a large suite.
Mix the skipper into your base test case so every test inherits the behaviour:
namespace Tests;
use CiaranMcNulty\VersionBasedTestSkipper\VersionBasedTestSkipper;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use VersionBasedTestSkipper;
}
/**
* @skipOnPhp <8.0
*/
public function testNamedArgumentsAreSupported(): void
{
// ...
}
On PHP 7.4 the test is marked skipped; on PHP 8.x it runs normally. That is the core value: your matrix stays green while version-specific tests remain in the suite and are visible as skipped rather than silently deleted.
Centralise the trait, annotate locally. Put the skipper on the project’s Tests\TestCase and keep the PHP-version annotations on individual test methods. This avoids repeating setup and makes the version assumptions discoverable next to the code they constrain.
Use it for “known incompatible”, not “hard requirement”. For a genuine requirement (the test cannot possibly run without a feature), prefer PHPUnit’s native @requires PHP >= 8.0 or #[RequiresPhp('>= 8.0')]. The skipper is most useful for the inverse case: the test is valid and should run, but a known bug or behaviour change makes it fail on certain versions.
Pair with a CI matrix. Run the suite on each supported PHP version (e.g. 7.4, 8.0, 8.1, 8.2) and let the annotations carve out the incompatible cells. The output becomes self-documenting: a skipped test on 7.4 and a green test on 8.1 is an explicit statement about your support surface.
Record why a skip exists. Treat each annotation like a TODO with an owner:
/**
* @skipOnPhp >=8.1
* @see https://github.com/acme/project/issues/1234
*/
public function testLegacySerialiserOutput(): void
{
// ...
}
This turns skips into a backlog you can grep, rather than permanent invisible holes.
Keep version-specific files separate. If a test file contains PHP 8 syntax (named arguments, enums, constructor promotion, match), an annotation cannot save it on PHP 7.4: PHP compiles the whole file before PHPUnit ever evaluates the skip. Put those tests in their own file and exclude that file from the older PHPUnit testsuite, or the suite will die with a parse error before any annotation is read.
Beware data providers. PHPUnit resolves data providers before setUp()-style skip hooks in many configurations. If the provider itself uses newer syntax or APIs, the test can fail before the skipper gets a chance to mark it skipped. Keep providers version-neutral and put version-sensitive logic in the test body.
Use it in PhpSpec-style contexts if applicable. The maintainer’s ecosystem is PhpSpec, so check whether the installed tag exposes an extension/example hook as well as a PHPUnit trait. The same constraint concept applies, but the integration point differs.
Prefer partial assertions when only behaviour differs. If a method is merely slower or emits a deprecation on one version, assert conditionally inside the test rather than skipping the whole thing; skipping loses coverage on every other assertion in that method.
composer.json, and treat an upgrade of PHPUnit or PHP as a trigger to re-verify the package. If the API surface you need is two lines of logic, consider inlining a version_compare(PHP_VERSION, ...) guard instead of carrying the dependency.src/ beats an afternoon of debugging an annotation that was never parsed.var_dump(PHP_VERSION_ID) and assert the skip count in CI if you need confidence.PHP_VERSION may contain suffixes (8.1.0-dev, 8.0.0RC1). version_compare handles these differently from PHP_VERSION_ID integer comparisons. Decide which semantics you want and verify the package uses the same one; ranges like <8.1 can behave surprisingly on release candidates.8.0 vs 8.0.0. Constraint strings that look equivalent to a human are not always equivalent to the comparator. Be explicit (<8.0.0) if the package delegates to version_compare.setUp() and your base class or a test also defines setUp(), resolve the conflict explicitly with insteadof/as or by calling the trait method. A skipped hook that never runs produces confusing “test not skipped” failures.markTestIncomplete for “this is a TODO”. Mixing them makes CI reports misleading.require-dev dependency; confirm composer install --no-dev does not autoload it and that your production classmap is unaffected.PHP_VERSION and the parsed constraint. One of those five is almost always the cause.How can I help you explore Laravel packages today?