rybakit/phpunit-extras
Extend PHPUnit with custom annotations, placeholders, requirements, and expectations. Build and integrate your own @tags, version/package/expression-based requirements, and reusable expectation helpers. Optional support for composer/semver, package-versions, and symfony/expression-language.
Installation Add via Composer:
composer require --dev rybakit/phpunit-extras
Requires PHPUnit 9.x+ and PHP 8.0+.
First Use Case
Annotate a test method with @dataProvider (if using custom providers) or leverage built-in annotations like @expectException (though PHPUnit already supports this—see below for extras).
Example:
use Rybakit\PHPUnitExtras\Annotations\DataProvider;
class MyTest extends \PHPUnit\Framework\TestCase
{
#[DataProvider('myProvider')]
public function testSomething($input, $expected)
{
$this->assertEquals($expected, someFunction($input));
}
public function myProvider()
{
return [
['input1', 'expected1'],
['input2', 'expected2'],
];
}
}
Where to Look First
src/Annotations/ for available annotations (e.g., @DataProvider, @SkipIf, @Timeout).src/Expectations/ for custom assertions (e.g., isIterableLike, containsOnlyInstancesOf).src/Traits/ for reusable test logic (e.g., HasAssertions, HasDataProviders).Data-Driven Testing
Use @DataProvider or @DataProviders (plural) to centralize test data:
#[DataProviders([
new DataProvider('provider1'),
new DataProvider('provider2'),
])]
public function testWithMultipleProviders($data) { ... }
Conditional Skipping
Skip tests dynamically with @SkipIf:
#[SkipIf(condition: 'app()->environment("production")')]
public function testOnlyInDev() { ... }
Custom Assertions Replace verbose assertions with fluent methods:
$this->assert->containsOnlyInstancesOf(stdClass::class, $collection);
Timeouts Enforce execution limits:
#[Timeout(seconds: 2)]
public function testFastOperation() { ... }
RefreshDatabase or MigrateFresh traits for seamless test isolation.@SkipIf to bypass flaky tests in CI:
#[SkipIf(condition: 'getenv("CI") === false')]
public function testFlakyInLocal() { ... }
getDataProvider() to migrate from @dataProvider to @DataProvider incrementally.Annotation Parsing
@SkipIf(condition: ...) vs. @skipIf).Annotation classes.DataProvider Conflicts
@dataProvider (PHPUnit native) and @DataProvider (rybakit) may cause ambiguity.Timeout Precision
@Timeout uses seconds (float allowed), but floating-point precision can cause edge cases.#[Timeout(seconds: 1.5)]).Trait Collisions
HasAssertions + custom assertions) may shadow methods.-v to see skipped/timeout reasons:
phpunit -v
phpunit --debug to inspect parsed annotations.@DataProviders to debug provider issues.Custom Annotations
Extend Rybakit\PHPUnitExtras\Annotations\Annotation to create your own:
class MyCustomAnnotation extends Annotation { ... }
Expectation Helpers
Add to src/Expectations/Assertions.php:
public function containsOnlyKeys(array $keys, array $array) { ... }
Global Setup
Use setUpBeforeClass() to preload data for @DataProvider-driven tests:
public static function setUpBeforeClass(): void
{
self::$sharedData = require __DIR__.'/data.php';
}
Laravel Integration
Bind the package’s services in TestCase:
use Rybakit\PHPUnitExtras\PHPUnitExtras;
protected function setUp(): void
{
$this->phpUnitExtras = new PHPUnitExtras();
}
How can I help you explore Laravel packages today?