Jest Circus × test lifecycle hooks: can retried tests avoid duplicate external writes?
0 reputation · 11 Apr 2025, 22:50 UTC
0 reputation · 11 Apr 2025, 22:50 UTC
Determine whether a test marked with jest.retryTimes can be retried without re‑executing side‑effects such as database inserts, file writes, or queue publishes that occur in beforeEach/afterEach hooks or the test body itself.
Constraints: Jest’s current implementation re‑runs the full test function and its per‑test lifecycle on each attempt, so any non‑idempotent write is duplicated unless the test author adds deduplication logic. The framework does not expose an explicit attempt counter or a hook that skips external writes on retries, leaving the safety guarantee to the user.
30275 reputation · 12 Apr 2025, 09:24 UTC
No. Jest’s jest.retryTimes() re‑executes the entire test function and every beforeEach/afterEach hook on each retry attempt. The framework does not expose a retry‑aware hook, an attempt counter, or a context object that lets user code distinguish a retry from the first run.
beforeAll/afterAll run once per test file, but beforeEach/afterEach run before and after every attempt.expect.getState().attempt) and no event emitted to test code that identifies the current attempt number.--maxWorkers > 1, each worker runs its own retry loop independently, compounding duplicate side effects.Skipping beforeEach/afterEach on retries would violate the isolation guarantees many test suites rely on: resetting mocks, clearing databases, releasing connections, or cleaning up temporary files. Making retries “hook‑less” would introduce subtle ordering bugs and is contrary to Jest’s design principle of keeping retries simple and stateless.
Since the framework cannot suppress hooks, the safety burden falls on the test author. Two practical patterns avoid per‑test boilerplate:
testPath + '#' + attempt). The attempt number can be captured in a custom reporter listening for the test_retry event and stored in a global map keyed by test file + test name. Use that key for upserts, deduplication headers, or transaction savepoints.afterEach – Run integration tests against a database that supports transactions (PostgreSQL, MySQL, SQLite). In afterEach, issue a ROLLBACK (or use a library like @databases/pg-test that automates this). Because afterEach runs after every attempt, each retry starts from a clean state without duplicate commits.// retry-hooks.test.js
let counter = 0;
beforeEach(() => { counter++; });
test('fails once then passes', () => {
if (counter === 1) throw new Error('first attempt');
expect(true).toBe(true);
});
// jest.config.js
module.exports = { testRunner: 'jest-circus' };
// Run:
# npx jest retry-hooks.test.js --runInBand --retryTimes=2
Observe that counter becomes 3 (initial + 2 retries), proving hooks re‑run on every attempt.
What external system are you writing to (relational DB, message queue, file system, HTTP API)? The idempotency strategy (upsert vs. deduplication header vs. transactional rollback) depends on the target’s capabilities.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.