testo/convention
Testo naming-convention plugin that discovers tests by file and class name patterns instead of explicit attributes. Ideal for adopting Testo in existing codebases with established conventions or integrating third-party suites without Testo attributes.
composer require --dev php-testo/testo testo/convention
testo.php (create if missing):
return [
'plugins' => [
'testo/convention' => [
'file_patterns' => ['*Test.php'],
'class_patterns' => ['*Test', 'TestCase'],
'method_patterns' => ['test*'],
],
],
];
vendor/bin/testo
Migrate a PHPUnit Test Suite:
TestCase classes to follow *Test convention (e.g., UserTest instead of UserTestCase).test (e.g., testUserCreation).vendor/bin/testo—no annotations needed.Feature/*Test.php, Unit/**/*Test.php).
'file_patterns' => [
'Feature/**/*Test.php',
'Unit/**/*Spec.php',
],
*Test, *Spec).
'class_patterns' => ['*Test', '*Spec'],
test*, it_*).
'method_patterns' => ['test*', 'it_*'],
Combine conventions with explicit attributes (for gradual migration):
'testo/convention' => [
'file_patterns' => ['*Test.php'],
'fallback_to_attributes' => true, // Use @test attributes if conventions fail
],
Wrap Testo’s discovery in a Laravel TestResolver:
// app/Providers/TestoServiceProvider.php
public function boot()
{
TestCase::macro('runTestoTests', function () {
$testo = new \Testo\Testo();
$testo->addPlugin(new \Testo\Convention\ConventionPlugin());
return $testo->run();
});
}
Add Testo to composer.json scripts:
"scripts": {
"test": "php artisan test && vendor/bin/testo",
"test:testo": "vendor/bin/testo --filter=Feature"
}
vendor/ or node_modules/:
'excluded_paths' => ['vendor/', 'node_modules/'],
TestCase:
use Testo\Assertions;
Assertions::extend('seeInDatabase', function ($expected, $actual) {
// Custom Laravel DB assertion logic
});
testo run --parallel alongside PHPUnit/Pest.False Positives/Negatives:
*Test (e.g., UserTestHelper).class_patterns or use method_patterns as a secondary filter.'method_patterns' => ['test*'], // Only methods starting with 'test' are tests
Namespace Conflicts:
App\Tests\UserTest but Laravel’s autoloader can’t resolve it.tests/ or app/Tests/ and autoloaded in composer.json:
"autoload": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
Performance Overhead:
file_patterns to test directories and cache results:'cache_discovery' => true, // Cache discovered tests for faster runs
Testo Plugin API Changes:
ConventionPlugin API shifts).composer.json:
"require-dev": {
"php-testo/testo": "1.0.0",
"testo/convention": "0.1.*"
}
IDE Support:
.idea/testRunConfigurations or vscode/settings.json to manually map Testo test files.Enable Verbose Output:
vendor/bin/testo --verbose
Dry Run:
vendor/bin/testo --dry-run
Log Discovery:
Configure Testo’s logger in testo.php:
'logging' => [
'level' => 'debug',
'output' => 'stderr',
],
Custom Conventions:
Extend the ConventionPlugin to add dynamic patterns:
use Testo\Convention\ConventionPlugin;
class CustomConventionPlugin extends ConventionPlugin
{
protected function getFilePatterns(): array
{
return array_merge(parent::getFilePatterns(), ['Custom/**/*Spec.php']);
}
}
Pre/Post-Processing: Hook into Testo’s lifecycle to modify discovered tests:
$testo->on('discovered', function ($tests) {
// Filter or transform tests before execution
return array_filter($tests, fn ($test) => str_contains($test->getName(), 'Feature'));
});
Laravel-Specific Extensions:
Create a TestoTestCase that integrates with Laravel’s TestCase:
use Illuminate\Foundation\Testing\TestCase as LaravelTestCase;
use Testo\TestCase as TestoTestCase;
class LaravelTestoTestCase extends LaravelTestCase
{
use TestoTestCase;
protected function createApplication()
{
$app = require __DIR__.'/../../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
return $app;
}
}
strtolower() in custom plugins for case-insensitive matching.glob() for pattern matching. Complex globs (e.g., {a,b}Test.php) may behave unexpectedly.fallback_to_attributes: true to allow both.How can I help you explore Laravel packages today?