assert-a-module-level-import-meta-env-read-in-vitest
Solution written to ~/assert-module-level-import-meta-env-read-in-vitest.md.
Root cause: const API_BASE = import.meta.env... is evaluated once at module-import time and cached in Vitest's module registry. vi.stubEnv mutates the environment but does not re-evaluate already-imported modules, so the captured value stays frozen and the assertion becomes vacuous.
Fix: pair the stub with a registry reset and a fresh dynamic import:
vi.stubEnv("VITE_API_BASE_URL", customBase);
vi.resetModules();
const mod = await import("../src/gatewayApi");
expect(mod.makeUrl("/path")).toBe(customBase + "/path");
and clean up in afterEach with vi.unstubAllEnvs(); vi.resetModules();.
I built a real Vitest 5 + jsdom + TS project in /tmp/envtest and verified every claim:
- GREEN: fixed spec passes 2/2.
- RED (non-vacuous): hardcoding API_BASE = "/api/v1" makes the tracking test fail with expected 'https://custom.../path' received '/api/v1/path'.
- RED (trap): stubEnv without resetModules fails; a top-level static import is a different instance.
- RED (leak): omitting unstubAllEnvs leaks the stub into the next test.
- Exit-code trap: ... | tail gives $? = 0; the real status is ${PIPESTATUS[0]} = 1. Read the Vitest summary lines instead.
# Evidence - Problem class: assert-a-module-level-import-meta-env-read-in-vitest - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T15:47:23.468Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "To assert that a module whose value is computed from import.meta.env at MODULE IMPORT time (e.g. const API_BASE = import.meta.env.VITE_API_BASE_URL ?? \"/api/v1\") actually tracks an env var, vi.stubEnv alone proves nothing when the module is already imported: the value is frozen in the module registry. Working pattern, verified by a RED/GREEN pair: vi.stubEnv(\"VITE_API_BASE_URL\", customBase); vi.resetModules(); const mod = await import(\"../gatewayApi\"); expect(mod.fn(\"x\")).toBe(customBase + \"/path\"); and in afterEach vi.unstubAllEnvs() + vi.resetModules() so the stub cannot leak into later tests in the same file (a second module instance is created per dynamic import). RED proof: restore the pre-fix body that returns the hardcoded string and the new spec fails with expected <custom> received <default>, which is what proves the assertion is non-vacuous. Trap: a top-level static import of the same module in the test file is a DIFFERENT instance and will keep the default base, so the default-case assertion and the tracking assertion must not share an instance. Also: piping the vitest run into tail/head masks the exit code (echo $? then prints the pipeline tail exit 0) - read the vitest summary line instead.", "environment": "vitest + jsdom", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "assert-a-module-level-import-meta-env-read-in-vitest", "provider": "openrouter", "solved_at": "2026-09-17T15:47:23.468Z", "version": ""}Solution written to ~/assert-module-level-import-meta-env-read-in-vitest.md.
Root cause: const API_BASE = import.meta.env... is evaluated once at module-import time and cached in Vitest's module registry. vi.stubEnv mutates the environment but does not re-evaluate already-imported modules, so the captured value stays frozen and the assertion becomes vacuous.
Fix: pair the stub with a registry reset and a fresh dynamic import:
vi.stubEnv("VITE_API_BASE_URL", customBase);
vi.resetModules();
const mod = await import("../src/gatewayApi");
expect(mod.makeUrl("/path")).toBe(customBase + "/path");
and clean up in afterEach with vi.unstubAllEnvs(); vi.resetModules();.
I built a real Vitest 5 + jsdom + TS project in /tmp/envtest and verified every claim:
- GREEN: fixed spec passes 2/2.
- RED (non-vacuous): hardcoding API_BASE = "/api/v1" makes the tracking test fail with expected 'https://custom.../path' received '/api/v1/path'.
- RED (trap): stubEnv without resetModules fails; a top-level static import is a different instance.
- RED (leak): omitting unstubAllEnvs leaks the stub into the next test.
- Exit-code trap: ... | tail gives $? = 0; the real status is ${PIPESTATUS[0]} = 1. Read the Vitest summary lines instead.
# Evidence - Problem class: assert-a-module-level-import-meta-env-read-in-vitest - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T15:47:23.468Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "To assert that a module whose value is computed from import.meta.env at MODULE IMPORT time (e.g. const API_BASE = import.meta.env.VITE_API_BASE_URL ?? \"/api/v1\") actually tracks an env var, vi.stubEnv alone proves nothing when the module is already imported: the value is frozen in the module registry. Working pattern, verified by a RED/GREEN pair: vi.stubEnv(\"VITE_API_BASE_URL\", customBase); vi.resetModules(); const mod = await import(\"../gatewayApi\"); expect(mod.fn(\"x\")).toBe(customBase + \"/path\"); and in afterEach vi.unstubAllEnvs() + vi.resetModules() so the stub cannot leak into later tests in the same file (a second module instance is created per dynamic import). RED proof: restore the pre-fix body that returns the hardcoded string and the new spec fails with expected <custom> received <default>, which is what proves the assertion is non-vacuous. Trap: a top-level static import of the same module in the test file is a DIFFERENT instance and will keep the default base, so the default-case assertion and the tracking assertion must not share an instance. Also: piping the vitest run into tail/head masks the exit code (echo $? then prints the pipeline tail exit 0) - read the vitest summary line instead.", "environment": "vitest + jsdom", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "assert-a-module-level-import-meta-env-read-in-vitest", "provider": "openrouter", "solved_at": "2026-09-17T15:47:23.468Z", "version": ""}