Every test must be independent — no shared mutable state, no execution order dependencies. When tests share state, they pass in isolation but fail together (or worse, fail randomly), creating flaky CI that wastes hours of debugging time.
Why This Matters
Shared mutable state between tests creates order-dependent results: test A modifies a shared variable, test B reads it and passes, but when test B runs first, it fails. These flaky tests erode team trust in the test suite. Developers start ignoring failures ("it's probably just flaky") and stop running tests locally. The test suite becomes a liability that slows development instead of catching bugs.
Test isolation means every test creates its own state, runs independently, and cleans up after itself. When tests share mutable state, the results depend on execution order:
Test A creates a user in a shared database
Test B queries for users and expects exactly one
Run A then B: passes
Run B then A: fails
Run A, B, C, B: fails because C also creates a user
These failures are non-deterministic. They pass locally but fail in CI (or vice versa). They pass alone but fail in a suite. Developers spend hours debugging a test that "should work," only to discover it depends on side effects from another test.
Flaky tests are worse than no tests. They train developers to ignore failures.
The rule
Every test must:
Create all the state it needs (no relying on state from other tests)
Not modify shared globals, singletons, or module-level variables
Clean up any external state it creates (database rows, files, environment variables)
Produce the same result regardless of execution order or parallelism
Bad example
// BAD: shared mutable state between testslet testUser: User;describe("UserService", () => { it("creates a user", async () => { // This test creates state that other tests depend on testUser = await userService.createUser({ name: "Alice", email: "a@b.com" }); expect(testUser.id).toBeDefined(); }); it("finds the user by email", async () => { // This test DEPENDS on the previous test running first const found = await userService.findByEmail("a@b.com"); expect(found?.id).toBe(testUser.id); // fails if run independently }); it("updates the user", async () => { // Also depends on test 1 — and modifies shared state await userService.updateUser(testUser.id, { name: "Bob" }); testUser.name = "Bob"; // mutates shared state! });});
Good example
// GOOD: every test creates its own statedescribe("UserService", () => { it("creates a user", async () => { const user = await userService.createUser({ name: "Alice", email: `alice-${randomId()}@test.com`, }); expect(user.id).toBeDefined(); }); it("finds a user by email", async () => { // Creates its own user — doesn't depend on other tests const email = `find-test-${randomId()}@test.com`; await userService.createUser({ name: "Alice", email }); const found = await userService.findByEmail(email); expect(found?.email).toBe(email); }); it("updates a user", async () => { // Creates its own user const user = await userService.createUser({ name: "Alice", email: `update-test-${randomId()}@test.com`, }); await userService.updateUser(user.id, { name: "Bob" }); const updated = await userService.findById(user.id); expect(updated?.name).toBe("Bob"); });});
How to detect
Look for shared mutable variables declared outside test blocks: