π retry
retry(attempts: number, cb: () => void)|retry(config: RetryConfig, cb: () => void)
retry is a helper designed to detect flaky tests by re-running failed tests a specified number of times.
History
| Version | Changes |
|---|---|
| v4.4.0 | retry helper. |
retry is a diagnostic tool, not a solution.
Use retry to identify unstable tests that need fixing. A test that requires retries is a test that should be investigated and corrected. Relying on retries to pass tests masks underlying issues and leads to unreliable test suites.
Basic Usageβ
Simple retryβ
Retry a test up to 3 times:
import { retry, it, assert } from 'poku';
await retry(3, () => {
it('flaky test', () => {
// This test may fail occasionally
assert.strictEqual(Math.random() > 0.5, true);
});
});
With configuration objectβ
Specify attempts and delay between retries:
import { retry, it, assert } from 'poku';
await retry({ attempts: 3, delay: 1000 }, () => {
it('flaky test', () => {
// Retry up to 3 times with 1 second delay between attempts
assert.strictEqual(Math.random() > 0.5, true);
});
});
Nested retriesβ
You can nest retry blocks for complex scenarios:
import { retry, it, assert } from 'poku';
await retry(2, async () => {
await retry(3, () => {
it('nested flaky test', () => {
// Outer: 2 attempts, Inner: 3 attempts per outer attempt
assert.strictEqual(Math.random() > 0.5, true);
});
});
});
Retry around describeβ
Wrap entire describe blocks:
import { retry, describe, it, assert } from 'poku';
await retry(2, () => {
describe('flaky suite', () => {
it('test 1', () => {
assert.strictEqual(1, 1);
});
it('test 2', () => {
// This test may fail occasionally
assert.strictEqual(Math.random() > 0.5, true);
});
});
});
Configurationβ
RetryConfigβ
type RetryConfig = {
attempts: number; // Maximum number of attempts (including the first run)
delay?: number; // Delay in milliseconds between retries (default: 0)
};
When to Use retryβ
β Appropriate use casesβ
- Identifying flaky tests: Temporarily add
retryto confirm a test is unstable - Investigating failures: Use
retrywhile debugging to gather more information - External dependencies: Tests that depend on external services with known instability (use sparingly)
β Inappropriate use casesβ
- Hiding broken tests: Don't use
retryto make failing tests pass - Permanent solution: If a test needs retries, it needs fixing
- Masking race conditions: Fix the underlying timing issue instead
Best Practicesβ
- Use temporarily: Add
retryto identify flakiness, then remove it after fixing the root cause - Investigate failures: When a test passes with
retrybut fails without it, investigate why - Fix the test: Address the underlying issue (timing, state, external dependencies)
- Remove
retry: Once fixed, remove theretrywrapper to ensure the test is stable
A healthy test suite should have zero retry usage. If you find yourself adding retry frequently, it's a sign that your tests need architectural improvements.
How It Worksβ
retry uses a stack-based context to support nested retries:
- When a test fails inside a
retryblock, it marks the current context as failed - The
retryfunction re-executes the entire block up toattemptstimes - Nested
retryblocks work independently, each with their own attempt counter describeblocks propagate their failure status to the outermostretrycontext
Memory Efficiency
retry uses lazy allocation: the stack is only created when retry is first called and reset to null when empty. If you never use retry, there's zero overhead.
Examplesβ
Detecting a race conditionβ
import { retry, it, assert } from 'poku';
// This test fails intermittently due to a race condition
await retry(5, async () => {
await it('async operation completes', async () => {
const result = await someAsyncOperation();
assert.strictEqual(result, 'expected');
});
});
// After identifying the flakiness, fix the race condition:
// - Add proper synchronization
// - Use deterministic timing
// - Mock external dependencies
// Then remove the retry wrapper
Testing external API with known instabilityβ
import { retry, it, assert } from 'poku';
// External API occasionally returns 503
await retry({ attempts: 3, delay: 2000 }, async () => {
await it('external API responds', async () => {
const response = await fetch('https://unstable-api.example.com/data');
assert.strictEqual(response.status, 200);
});
});
// Better approach: mock the external API in tests
// Use retry only during development to identify the instability
Relatedβ
describe: Group testsit: Define individual testsskip: Skip testsbeforeEach/afterEach: Setup and teardown