#6486·zod

4.5.0+: safeParse (and likely other methods) moved off own-instance properties, breaking Vitest's vi.mock() automocking

Author: petergoldsteinCreated Aug 28, 2026Updated Sep 12, 2026

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

bash
mkdir repro && cd repro
npm init -y
npm install -D vitest
npm install [email protected]   # or [email protected] to see it pass

schema.mjs:

javascript
import { z } from "zod";
export const MySchema = z.object({ content: z.string().optional() });

repro.test.mjs:

javascript
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);
  });
});
bash
npx vitest run

With [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
javascript
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.0 and 4.5.1 (both reproduce; 4.4.3 does not)
  • vitest: 4.1.11
  • node: 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.