4.5.0+: safeParse (and likely other methods) moved off own-instance properties, breaking Vitest's vi.mock() automocking
Summary
Between 4.4.3 and 4.5.0/4.5.1, schema instance methods like .safeParse moved from being own, enumerable properties on each schema instance to a non-enumerable getter on the prototype. This appears to be part of the 4.5 "9x reduction in schema memory footprint" change (z.compile()), but it silently breaks Vitest's vi.mock() automocking for any module that exports a Zod schema — a very common pattern (mocking a serializer/validator module in tests).
Repro
mkdir repro && cd repro
npm init -y
npm install -D vitest
npm install [email protected] # or [email protected] to see it passschema.mjs:
import { z } from "zod";
export const MySchema = z.object({ content: z.string().optional() });repro.test.mjs:
import { describe, it, expect, vi } from "vitest";
import { MySchema } from "./schema.mjs";
vi.mock("./schema.mjs");
describe("vi.mock() automocking a zod schema", () => {
it("safeParse should be auto-mocked", () => {
console.log("isMockFunction:", vi.isMockFunction(MySchema.safeParse));
console.log("own property?", Object.prototype.hasOwnProperty.call(MySchema, "safeParse"));
expect(vi.isMockFunction(MySchema.safeParse)).toBe(true);
});
});npx vitest runWith [email protected]: passes.
With [email protected] or 4.5.1: fails —
isMockFunction: false
own property? true(Vitest's automock does create an own safeParse property on the mocked instance, but it isn't a real mock function — vi.isMockFunction() returns false on it, so vi.mocked(schema.safeParse).mockReturnValue(...) throws Not a mock function! in any test relying on it.)
Root cause (as far as I can tell from the outside)
Directly comparing instance shape between versions for the same schema (z.object({ content: z.optional(z.string()) })):
4.4.3 |
4.5.1 |
|
|---|---|---|
| Own properties on instance | 21 | 3 |
safeParse location |
own, enumerable, instance property | non-enumerable getter on the prototype |
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(schema), "safeParse")
// 4.5.1: { enumerable: false, writable: undefined, configurable: true, get: [Function] }This matches the 4.5 release notes' memory-footprint work (and looks related to #5760, which was about reducing per-instance own-property count). The optimization itself seems reasonable — the issue is specifically that it changes safeParse (and presumably parse, parseAsync, etc.) from a shape Vitest's automocker can walk and replace, into one it can't.
Environment
zod:4.5.0and4.5.1(both reproduce;4.4.3does not)vitest:4.1.11node:v24.20.0
Impact
Any codebase that does vi.mock("./some-module-exporting-a-zod-schema") and then asserts/overrides .safeParse (or presumably .parse) on the auto-mocked schema will silently break on upgrade to 4.5.x — the mock target exists but isn't a mock, so vi.mocked(schema.safeParse).mockReturnValue(...) throws at runtime instead of failing to compile or giving a clear signal ahead of time.
Happy to provide more detail or test a fix if useful — this is a fresh release (published today) so I wanted to flag it while the exact repro is fresh.
Source: colinhacks/zod