testo/repeat
Testo repeat policy plugin. Re-runs a test multiple times in a single run to surface flaky behavior, catch intermittent regressions, and verify consistent results. Opt-in per test or per test class and works alongside other Testo plugins.
composer require --dev testo/repeat
testo.php):
return [
'plugins' => [
Testo\Repeat\RepeatPlugin::class,
],
];
use Testo\Repeat\Repeat;
class ExampleTest
{
#[Repeat(3)] // Runs this test 3 times
public function test_something_flaky()
{
// Test logic here
}
}
Or for a class-level repeat:
#[Repeat(2)]
class ExampleTest
{
// All tests in this class run 2 times
}
Identify flaky tests in CI:
@Repeat(3) to tests known to fail intermittently (e.g., API calls, database operations).maxFailures to fail the test only if it fails all 3 times (default: maxFailures = 0 means any failure counts).
#[Repeat(3, maxFailures: 1)] // Fails if ≥1 failure out of 3
Opt-In Repeat Testing:
#[Repeat(5)]
public function test_payment_processing()
{
// Runs 5 times; fails if any iteration fails (default maxFailures=0)
}
#[Repeat(2)]
class FlakyApiTest
{
// All tests here run twice
}
Failure Threshold Tuning:
maxFailures to balance sensitivity and noise:
#[Repeat(3, maxFailures: 1)] // Fails if ≥1 failure (strict)
#[Repeat(3, maxFailures: 2)] // Fails if ≥2 failures (lenient)
Integration with Testo Plugins:
use Testo\Assert\Assert;
use Testo\Repeat\Repeat;
#[Repeat(3)]
public function test_user_creation()
{
Assert::true(User::create(['name' => 'Test'])->exists());
}
CI-Specific Configuration:
// In testo.php
'repeat' => [
'default_repeats' => env('CI') ? 3 : 1,
],
Repeat Laravel Test Cases:
TestCase and annotate:
use Illuminate\Foundation\Testing\TestCase as LaravelTestCase;
use Testo\Repeat\Repeat;
#[Repeat(2)]
class UserTest extends LaravelTestCase
{
// Laravel test logic
}
Repeat Database Transactions:
#[Repeat(5)]
public function test_concurrent_orders()
{
$this->seed();
// Simulate concurrent requests
}
Repeat API Tests:
#[Repeat(3, maxFailures: 1)]
public function test_api_payment()
{
$response = $this->post('/pay', ['amount' => 100]);
$response->assertOk();
}
Compose with Laravel Factories:
#[Repeat(3)]
public function test_factory_creation()
{
$user = User::factory()->create();
Assert::true($user->exists());
}
Test Hangs or Timeouts:
False Positives from State Leakage:
#[Repeat(3)]
public function test_with_clean_state()
{
$this->withoutExceptionHandling();
$this->artisan('migrate:fresh'); // Reset DB before each repeat
// Test logic
}
maxFailures Misconfiguration:
maxFailures too high may mask real issues.maxFailures: 0 (fail on any failure) and adjust as needed.Plugin Conflicts:
Laravel-Specific Issues:
actingAs() per repeat:
#[Repeat(2)]
public function test_authenticated_routes()
{
$user = User::factory()->create();
$this->actingAs($user); // Reset auth per repeat
$this->get('/dashboard')->assertOk();
}
Log Repeat Iterations:
#[Repeat(3)]
public function test_debug_repeats()
{
\Log::info('Repeat iteration: ' . $this->getRepeatCount());
// Test logic
}
Isolate Flaky Tests:
#[Repeat(10)]
public function test_high_risk_operation()
{
// ...
}
Check Testo’s Repeat Plugin Docs:
Repeat behavior.Custom Repeat Logic:
Repeat trait or plugin to add dynamic repeat counts:
use Testo\Repeat\Repeat;
#[Repeat]
public function test_dynamic_repeats()
{
$repeats = env('TEST_REPEATS', 3);
$this->setRepeatCount($repeats);
// Test logic
}
Post-Repeat Actions:
#[Repeat(3)]
public function test_with_hooks()
{
$this->afterRepeat(function () {
\Log::info('Test completed ' . $this->getRepeatCount() . ' times');
});
// Test logic
}
Global Repeat Configuration:
testo.php:
'repeat' => [
'default_repeats' => 2,
'max_failures' => 0, // Fail on any failure
],
Skip Repeats in Local Dev:
#[Repeat(app()->environment('production') ? 3 : 1)]
public function test_production_only_repeats()
{
// ...
}
Parallelize Repeats (Advanced):
Exclude Stable Tests:
#[Repeat(1)] // Disable repeats for stable tests
public function test_stable_operation()
{
// ...
}
How can I help you explore Laravel packages today?