PRACTICAL GUIDE / TypeScript typed mocks

TypeScript Typed Mocks Without Unsafe Casts

Build TypeScript typed mocks with jest.mocked, vi.mocked, satisfies, and fixture factories so signature or return-shape drift fails during compilation.

By The Testing AcademyUpdated July 25, 202621 min read
All field guides
In this guide11 sections
  1. What Makes a TypeScript Mock Truly Typed?
  2. How Do Unsafe Casts Hide Mock Regressions?
  3. jest.mocked, vi.mocked, and Typed fn: Which Tool Should You Use?
  4. A Numbered Workflow for Replacing Unsafe Test Casts
  5. Code Example: Replace as jest.Mock with jest.mocked
  6. How Should You Build Complete Fixtures with Partial Overrides?
  7. Code Example: Keep a Vitest Dependency Mock Signature Intact
  8. Typed Full Mocks vs Partial Dependencies vs Fixture Factories
  9. Which Casts Are Acceptable in TypeScript Tests?
  10. Frequently Asked Questions About TypeScript Typed Mocks
  11. What is a typed mock in TypeScript?
  12. When should I use jest.mocked instead of jest.Mock?
  13. What is the Vitest equivalent of jest.mocked?
  14. Why is as unknown as unsafe in test fixtures?
  15. Should a mock factory return Partial of the production type?
  16. Do typed mocks remove the need for runtime tests?
  17. Next Steps: Make Mock Drift a Compile Failure

What you will learn

  • What Makes a TypeScript Mock Truly Typed?
  • How Do Unsafe Casts Hide Mock Regressions?
  • jest.mocked, vi.mocked, and Typed fn: Which Tool Should You Use?
  • A Numbered Workflow for Replacing Unsafe Test Casts

TypeScript typed mocks preserve the production function signature through jest.mocked(), vi.mocked(), jest.fn<typeof dependency>(), or vi.fn<typeof dependency>(). Valid fixtures use satisfies, complete factories, and narrow Partial<T> override inputs instead of force-casting incomplete values. The goal is simple: when a parameter, promise result, or required field changes, compilation should fail at the stale test setup.

That compile-time failure is only one layer of evidence. A typed double can still model unrealistic behavior, return the wrong business scenario, or omit an important runtime assertion. Good tests combine signature safety, valid fixture construction, controlled dependency behavior, and assertions against the subject's observable result.

What Makes a TypeScript Mock Truly Typed?

A mock is truly typed when it keeps the contract of the dependency it replaces. Parameter count and types matter. The return type matters. For asynchronous code, the resolved value matters. For generators, the yielded and returned values matter. Object fixtures must satisfy required fields. The runner then adds call tracking and behavior configuration without replacing those contracts with any.

Consider a production function:

fetchUser(id: string): Promise<User>

A typed mock rejects fetchUserMock(42), rejects mockResolvedValue("Ada"), and rejects a result object missing a required displayName. A broad mock that accepts any arguments and any result gives the test runner flexibility at the cost of the compiler's most useful warnings.

There are four related but distinct test values:

  • A mocked imported function, where jest.mocked(fetchUser) or vi.mocked(fetchUser) exposes runner methods on the real signature.
  • A new function double, where a generic fn or typed implementation defines the dependency contract.
  • A fixture object, where satisfies User or a factory proves complete shape.
  • A partial dependency interface, where the subject intentionally consumes a smaller contract than the full production service.

Do not collapse these into one casting pattern. Function typing and fixture typing solve different problems. A typed function can still return a force-cast bad object. A valid object can still be configured on a mock whose parameters have become unchecked.

Overloads and generics need deliberate handling. A helper inferred from one implementation may retain only the chosen callable form rather than every production overload. If callers rely on several forms, derive the double from typeof dependency and add cases for each supported call. For a generic dependency, decide whether the subject needs the full generic relationship or one concrete specialization. A broad mock that accepts the union of every imaginable input is not equivalent to preserving how input and output types relate.

Async contracts are more than Promise labels. mockResolvedValue should receive the resolved production type, while a synchronous mockReturnValue should be rejected for a promise-returning function unless that runner API wraps it intentionally. Generator, callback, and abort-signal behavior also belong to the callable type. Keeping those pieces visible prevents a test fake from using an execution model the subject could never receive.

The Jest mock function reference documents inferred implementations, generic function types, and jest.mocked. The Vitest vi reference provides the corresponding tools. A strict project configuration makes their signals more useful; see enabling TypeScript strict mode in test frameworks.

How Do Unsafe Casts Hide Mock Regressions?

The repository records a concrete failure in src/db/seed-data/challenges/expansion-js-ts.ts. Inside JS_TS_CHALLENGES, the entry with slug javascript-testing-typed-mocks-scenario is titled "The Cast That Smuggled a Regression." A refactor renames User.name to User.displayName, while a stale test keeps returning { name: "Ada Lovelace", plan: "pro" }.

Two assertions remove every place where the compiler could object:

const fetchUserMock = fetchUser as jest.Mock;

This turns a known fetchUser signature into a broad runner type. Its mockResolvedValue no longer needs to follow Promise<User>.

{ name: "Ada Lovelace", plan: "pro" } as unknown as User

This first moves the literal to unknown, then commands TypeScript to treat it as User. The compiler is not asked whether displayName exists. The test may remain green because another output location still contains the old name property, even while production renders an undefined display name.

The key lesson is not that TypeScript missed a cross-file refactor. The test explicitly removed the connection. The repository scenario repairs the function with jest.mocked(fetchUser) and repairs data through makeUser(overrides: Partial<User>): User or a checked literal using satisfies User.

Use TypeScript control-flow narrowing for assertion helpers when a runtime check should establish a safe type. Narrowing follows evidence. A double assertion does the opposite: it skips evidence.

The same concern applies to as any, broad runner mock casts, and untyped JSON fixtures. A comment may explain why a rare invalid-state test crosses a boundary, but ordinary valid fixtures should compile against the production contract.

jest.mocked, vi.mocked, and Typed fn: Which Tool Should You Use?

Choose based on where the double comes from. mocked helpers are for imports or objects that the runner has already replaced or spied on. fn creates a new double, which works well with dependency injection. Supplying an implementation often gives strong inference; supplying a function type is useful when the implementation is configured later.

ToolStarting valueContract retainedRunnerBest use
jest.mocked(source)imported mocked valuesource function or object typeJestexpose mock methods after module mocking
vi.mocked(source)imported mocked valuesource function or object typeVitestsame role in a Vitest suite
jest.fn<typeof dep>()new doubledependency function typeJestinjected dependency configured later
vi.fn<typeof dep>()new doubledependency function typeVitestinjected dependency configured later
vi.fn((arg: T) => value)typed implementationinferred parameters and resultVitestconcise behavior-specific dependency
untyped fn()new broad doublelittle until configuredeitheravoid when contract matters

Deep mocked object helpers may transform nested members in the type view. That does not make nested production values exist at runtime, and it does not automatically choose correct behavior. Prefer the smallest mocking surface that the subject actually consumes.

For imported module format concerns, read debugging ESM and CommonJS conflicts in TypeScript tests. Module replacement must succeed at runtime before jest.mocked() or vi.mocked() can provide a useful typed handle. The typing helper does not perform module mocking by itself.

If the subject accepts a dependency parameter, a typed fn may be cleaner than global module mocking. The dependency contract is visible, test isolation improves, and each case can supply a distinct implementation without relying on import timing.

A Numbered Workflow for Replacing Unsafe Test Casts

Refactor one dependency boundary at a time:

  1. Find the production function or interface and record its complete current signature.
  2. Remove as jest.Mock, as vi.Mock, as any, or as unknown as T from the test setup.
  3. Use jest.mocked() or vi.mocked() for an imported value that the runner has replaced.
  4. Use jest.fn<typeof dependency>(), vi.fn<typeof dependency>(), or a typed implementation for a new double.
  5. Make every configured return value compile against the real result type.
  6. Centralize complete fixture defaults in a factory and accept only narrow Partial<T> overrides.
  7. Use satisfies for literal fixtures whose precise inferred properties are useful.
  8. Add a compile-failure example with @ts-expect-error only when documenting the rejected contract.
  9. Run tsc --noEmit and the runtime test command in CI as separate required checks.
  10. Review whether the mock behavior still represents production failure, success, timing, and side-effect cases.

Do not replace one broad assertion with a more complicated broad assertion. as jest.MockedFunction<any> still includes any. A generic wrapper that returns T by casting an object may simply move the hole into shared code. The factory implementation itself must construct a complete valid value.

Keep the type-check command responsible for test files. Confirm that the relevant tests are included by the TypeScript project used in CI. A typed mock offers no protection if the test file is excluded from compilation.

Use compiler failures as migration inventory. After removing a broad cast, collect each error by category: stale parameters, invalid fixture shape, wrong async result, or runner setup. Repair the production relationship rather than suppressing the message. If many files share the same stale fixture, introduce one complete factory and move cases gradually. If the same imported function is cast repeatedly, expose one typed mock handle in the suite setup without exporting it as an unconstrained generic.

Review call assertions too. A mock may accept correct arguments while the test asserts only call count. Add toHaveBeenCalledWith where values carry behavior, but do not snapshot every incidental option. Type checking proves the asserted values are assignable; runtime call assertions prove the subject actually supplied them.

The strict Playwright TypeScript framework guide covers suite-wide configuration patterns. The mock workflow here is runner-agnostic and applies to Jest or Vitest unit tests as long as TypeScript checks the files.

Code Example: Replace as jest.Mock with jest.mocked

The following example rebuilds the repository scenario from the javascript-testing-typed-mocks-scenario entry in JS_TS_CHALLENGES. It retains the imported fetchUser signature and uses complete checked data.

TypeScript
import { expect, jest, test } from "@jest/globals";
import { fetchUser } from "./api";
import { renderUserCard } from "./user-card";
import type { User } from "./types";

jest.mock("./api");

const fetchUserMock = jest.mocked(fetchUser);

function makeUser(overrides: Partial<User> = {}): User {
  return {
    displayName: "Ada Lovelace",
    plan: "pro",
    ...overrides,
  };
}

const freeUser = {
  displayName: "Grace Hopper",
  plan: "free",
} satisfies User;

test("renders the display name", async () => {
  fetchUserMock.mockResolvedValue(makeUser());

  const html = await renderUserCard("u_42");

  expect(fetchUserMock).toHaveBeenCalledWith("u_42");
  expect(html).toContain("Ada Lovelace");
});

test("renders a free user", async () => {
  fetchUserMock.mockResolvedValue(freeUser);
  expect(await renderUserCard("u_7")).toContain("Grace Hopper");
});

// After the rename, this remains a compile error:
// @ts-expect-error User requires displayName, not the removed name property.
fetchUserMock.mockResolvedValue({ name: "Stale", plan: "pro" });

jest.mocked(fetchUser) does not change the runtime value by itself; jest.mock("./api") performs module replacement. The helper supplies the typed view that connects mock methods to the imported function. mockResolvedValue must now accept the resolved type of the production promise.

The @ts-expect-error line is documentation for a deliberate negative type example, not production test setup. TypeScript will also complain if the line unexpectedly stops producing an error, which helps keep educational examples accurate. Remove such examples if they create noise in the suite.

The freeUser literal keeps its precise inferred properties while TypeScript checks compatibility. The TypeScript 4.9 notes explain the satisfies operator. For broader fixture configuration patterns, see using satisfies for type-safe test configuration.

How Should You Build Complete Fixtures with Partial Overrides?

Place Partial<User> at the override input, not at the factory output. The defaults must be complete, the merge must return User, and consumers should receive the same shape they would receive in production.

This factory boundary creates useful refactor pressure. If production adds a required locale, the factory stops compiling until a valid default is chosen. One repair updates all tests that use the factory. Individual cases can still override locale when behavior depends on it.

Be careful with nested objects. A shallow Partial<User> makes top-level properties optional but does not make nested fields recursively optional. That is often desirable because it avoids constructing half-valid nested values. If a test needs nested customization, add a nested factory or a purpose-built override type rather than applying an unchecked deep merge.

Spread order is part of the factory contract. Defaults must come first and overrides last when callers are allowed to replace fields. If required fields must never become undefined, the override type should prevent that under the project's optional-property settings, or the factory should validate and reconstruct those fields explicitly. Do not rely on a return annotation to make a runtime undefined disappear; annotations check assignability of source expressions but do not repair values.

Keep identifiers and timestamps deterministic unless uniqueness is the behavior under test. Random defaults make snapshots and failure reproduction harder, while current-time defaults can blur whether a value came from the scenario or test clock. A complete factory can accept explicit overrides for these fields and still use readable stable defaults for ordinary cases.

Factories should model valid domain states, not every possible invalid input. Test malformed external data before it becomes User, through the parser or validator that production uses. If the domain itself represents an invalid or transitional state as a tagged union, add a properly typed factory for that variant instead of mutating a valid fixture through a cast.

The TypeScript utility reference defines Partial<Type> as a type with optional properties. Optional does not mean validated at runtime, and it does not make arbitrary keys legal. Excess-property checks and satisfies can still catch misspellings on object literals.

Compare these boundaries:

PatternCompile-time resultMaintenance effect
makeUser(overrides: Partial<User>): Usercomplete output requiredcentral repair point
makeUser(): Partial<User>missing fields spread to callersweak consumer contract
literal satisfies Userexact literal checkedgood for named fixed cases
literal as Usercompatibility may be forcedrefactor defects can hide
external data parsed at runtimevalue checked at boundaryappropriate for untrusted fixtures

The TypeScript utility types for test data builders explores builder design in more depth. The generics guide for reusable fixtures helps when factories share safe patterns across several models.

Do not let factory defaults erase scenario meaning. A case testing a suspended account should state status: "suspended" explicitly even if the default is active. Type safety keeps the value valid; readable overrides keep the behavior intentional.

Code Example: Keep a Vitest Dependency Mock Signature Intact

src/lib/companion/openrouter-core.test.ts supplies a cast-free example. Its openWithFallback tests define generator helpers for success, empty output, immediate failure, and mid-stream failure. Each open double uses vi.fn((model: string) => ...), so argument inference and generator results remain connected to the implementation.

The adapted example below names the dependency type and retains the exact behaviors exercised by openWithFallback.

TypeScript
import { describe, expect, it, vi } from "vitest";
import {
  OpenRouterError,
  openWithFallback,
} from "./openrouter-core";

type OpenModel = (model: string) => AsyncGenerator<string>;

async function* chunks(...parts: string[]): AsyncGenerator<string> {
  for (const part of parts) yield part;
}

async function* failsImmediately(
  model: string,
): AsyncGenerator<string> {
  if (model) throw new OpenRouterError(model, 500);
  yield "";
}

describe("openWithFallback", () => {
  it("uses a typed fallback after an immediate primary failure", async () => {
    const open = vi.fn<OpenModel>((model) =>
      model === "primary" ? failsImmediately(model) : chunks("saved"),
    );

    const result = await openWithFallback({
      primary: "primary",
      fallback: "fallback",
      open,
    });

    expect(result.model).toBe("fallback");
    const received: string[] = [];
    for await (const part of result.textStream) received.push(part);
    expect(received).toEqual(["saved"]);
    expect(open).toHaveBeenNthCalledWith(1, "primary");
    expect(open).toHaveBeenNthCalledWith(2, "fallback");
  });
});

// These examples fail compilation if uncommented:
// vi.fn<OpenModel>((model: number) => chunks(String(model)));
// vi.fn<OpenModel>(() => Promise.resolve("not a generator"));

The type states that open accepts a model string and returns an asynchronous generator of strings. An incompatible argument or promise-returning implementation is rejected before the runtime suite starts. The actual tests still need to prove fallback selection, stream delivery, call counts, and mid-stream behavior.

Different helper generators are preferable to one highly configurable fake here. chunks, empty, failsImmediately, and failsMidStream make behavior visible. Their explicit return annotations ensure each helper also follows the stream contract.

For errors caused by impossible narrowed types, debugging TypeScript never and exhaustiveness errors explains another compiler feedback path. Do not silence it with a cast until the control flow and intended contract are understood.

Typed Full Mocks vs Partial Dependencies vs Fixture Factories

Not every test needs a full mocked service. Sometimes the subject should depend on a smaller interface. If a function only needs open(model), accepting an OpenModel dependency is clearer than accepting a large provider client and constructing dozens of irrelevant mock methods.

TechniqueContract retainedSetup costRefactor responseRuntime limitationSuitable boundary
typed imported mockmodule export signaturemediumimport changes fail compilationmodule behavior is replacedexisting module dependency
typed injected functionexact callable signaturelowparameters and results fail compilationfake may be unrealisticnarrow operation
partial dependency interfaceintentionally small interfacelowconsumed members stay checkedcan omit integration behaviordependency inversion
complete fixture factoryproduction object shapemedium oncerequired fields fail centrallydefault may hide scenario intentrepeated valid data
checked literal with satisfiescomplete target compatibilitylowlocal fixture failsrepeated setupnamed fixed case
runtime parserexternal value contracthigherinvalid data fails at runtimeneeds parser maintenanceuntrusted file or network data

A partial dependency is not an incomplete production object forced through a cast. It is a real interface designed around what the subject consumes. TypeScript then checks both the subject and each supplied implementation against that narrower contract.

Use Pick only when it communicates an intentional stable capability. A local interface can be clearer when the subject's abstraction should not change every time the large vendor client changes. In either form, production wiring must adapt the real dependency to the narrow contract, which lets TypeScript verify that tests and runtime code agree. The test should not invent a smaller shape that production never accepts.

Spies are another option when retaining most real behavior is safe. A typed spy can replace one method while leaving the rest of an object intact, but it also carries shared-state and cleanup risks. Restore it after each case and avoid spying on network or persistent side effects merely to reduce setup. Choose based on the boundary the subject owns, not on which syntax is shortest.

Runtime validation is still necessary at external boundaries. The guide to runtime schema validation with static TypeScript models explains why a declared type cannot validate JSON loaded from disk or a response. Typed mocks address compile-time test code, not untrusted runtime data.

Exact optional-property behavior can also affect fixtures. Modeling exactOptionalPropertyTypes in API tests distinguishes an absent property from a property explicitly assigned undefined. A factory should follow the project's chosen semantics.

Which Casts Are Acceptable in TypeScript Tests?

Prefer no assertion when inference, a typed helper, satisfies, narrowing, or a factory can express the contract. Permit a narrow assertion at a checked boundary when runtime evidence exists but TypeScript cannot carry it. Permit deliberate construction of an inaccessible invalid state only when the test's purpose requires it, the scope is minimal, and a comment explains the boundary.

Reject as unknown as T, as any, and broad runner mock casts for ordinary valid fixtures. They suppress exactly the errors a typed test suite should surface. Also reject shared helpers that hide those assertions behind generic names such as mockValue<T>().

Use @ts-expect-error for intentional compile-failure examples because it documents that an error is required. Do not use it to make normal test setup compile. A test that verifies runtime handling of malformed external input should usually pass unknown data through the same parser used in production instead of pretending the value is already a domain type.

Require both commands in CI:

  1. A type-check such as tsc --noEmit that includes production and test sources.
  2. The Jest or Vitest command that executes runtime behavior.

They detect different failures. Compilation finds stale signatures and shapes. Runtime tests find wrong branching, calls, effects, and results. A green runner does not imply that TypeScript checked the files, and a green compiler does not execute the scenario.

Add lint or review checks for the highest-risk escape hatches if the suite repeatedly regresses. A rule can flag explicit any or double assertions, but it cannot decide every legitimate boundary. Pair automation with a short policy: mocked imports use typed helpers, injected functions keep named signatures, ordinary fixtures are complete, and any suppressed type error explains the invalid state being tested.

Keep runner type packages and TypeScript versions aligned with configuration. An upgrade can alter helper inference even when runtime behavior stays the same. Run type checking before accepting generated snapshot or fixture updates so an apparent test-runner migration does not reintroduce broad casts as a shortcut.

Review the mock's return channel as carefully as its immediate value. A dependency returning Promise<User> needs a valid User in mockResolvedValue; a dependency returning AsyncGenerator<string> needs an implementation that yields strings and follows the expected failure timing. Replacing either with a loosely typed function can make a test model a state production cannot produce.

The repository's openWithFallback cases show why runtime variation still matters after signature checking. chunks() yields content, empty() completes without content, failsImmediately() throws before output, and failsMidStream() yields once before throwing. Each helper satisfies the generator contract, yet each drives a different branch. Type safety keeps the channel valid while runtime assertions prove fallback and propagation behavior.

Call assertions benefit from the same retained signature. When open accepts a model string, toHaveBeenCalledWith should use a valid model argument rather than a cast value. If production later adds a required options object, a typed double exposes stale calls, implementations, and expectations during compilation. A generic mock can leave all three outdated.

Check object factories for nested aliasing as well. A complete top-level return type does not guarantee that tests receive fresh mutable arrays or maps. If one test mutates a shared default, another can observe it. Create fresh nested values inside the factory where isolation matters, and let overrides replace them through an intentional boundary.

Keep factory names tied to valid domain states. makeUser() should return a usable User; a helper for malformed external input should return unknown or the raw transport shape and pass through production validation. Naming an invalid forced object makeUser() hides the difference between testing a domain object and testing boundary rejection.

During review, ask where the compiler would report a renamed required field. A healthy design points to the complete default, a satisfies literal, or the mocked result. If the answer is "nowhere until runtime," find the assertion or generic that removed the relationship. This question is more useful than counting casts without understanding their effect.

Advanced interview-style examples appear in TypeScript questions for senior SDETs, but the daily policy can remain short: retain real dependency types, construct complete values, and demand an explanation for any escaped check.

Frequently Asked Questions About TypeScript Typed Mocks

What is a typed mock in TypeScript?

It is a runner double that keeps the production callable or object contract while adding configuration and call history. Incompatible arguments, returns, and fixture shapes should fail compilation. Runtime assertions still prove what the subject did with the configured behavior.

When should I use jest.mocked instead of jest.Mock?

Use jest.mocked after Jest replaces an imported function or object and you need correctly typed mock methods. A broad jest.Mock cast can lose the source signature. Use jest.fn<typeof dependency>() when creating a new injected double.

What is the Vitest equivalent of jest.mocked?

Use vi.mocked for an already mocked import. Use vi.fn with a function type or typed implementation for a new double. The repository's openWithFallback tests demonstrate inference from vi.fn((model: string) => ...).

Why is as unknown as unsafe in test fixtures?

It bypasses structural compatibility instead of establishing it. Missing required fields and stale renamed fields can pass compilation. A complete factory, a satisfies literal, or runtime parsing at an unknown-data boundary preserves evidence.

Should a mock factory return Partial of the production type?

Usually it should accept Partial<T> overrides and return complete T. That centralizes valid defaults and keeps consumers honest. A partial return is appropriate only when the real consumer contract is intentionally partial.

Do typed mocks remove the need for runtime tests?

No. Types constrain setup but cannot prove call order, retries, side effects, thrown errors, stream timing, or realistic scenarios. Run type checking and the test runner as separate gates, and review whether each fake behavior represents the production boundary.

Next Steps: Make Mock Drift a Compile Failure

Search test sources for broad runner casts, double assertions, as any, and factories that return Partial without a partial production contract. Start with one high-value dependency. Restore its source signature through mocked or typed fn, then make every result fixture compile without force.

Add complete factories for repeated domain values, keep scenario-relevant overrides explicit, and include test files in tsc --noEmit. Preserve runtime cases such as the openWithFallback success, empty, immediate-failure, and mid-stream paths. The finished suite should fail early when types drift and still fail behaviorally when a validly typed double drives the wrong result.

// LIVE COURSE / THE TESTING ACADEMY

Playwright Automation Mastery

Go beyond Selenium. Master Playwright with JS/TS in 90 days.

From the instructor behind this guide.

Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 25, 2026 / Reviewed July 25, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Official jestjs.io reference

    jestjs.io

    Primary documentation selected and verified for the claims in this guide.

  2. 02
    Official vitest.dev reference

    vitest.dev

    Primary documentation selected and verified for the claims in this guide.

  3. 03
    Official typescriptlang.org reference

    typescriptlang.org

    Primary documentation selected and verified for the claims in this guide.

  4. 04
    Official typescriptlang.org reference

    typescriptlang.org

    Primary documentation selected and verified for the claims in this guide.

FAQ / QUICK ANSWERS

Questions testers ask

What is a typed mock in TypeScript?

A typed mock retains the production function's parameters, return value, promise behavior, and relevant object shape while adding runner methods for configuring and inspecting calls. TypeScript should reject incompatible arguments or results in the test. Runtime assertions are still required because types cannot prove that configured behavior represents production.

When should I use jest.mocked instead of jest.Mock?

Use jest.mocked when an imported function or object has been replaced by Jest and you need mock methods without losing its real signature. A broad jest.Mock annotation can discard parameter and return contracts. For a new standalone double, jest.fn with the dependency function type is often the clearer choice.

What is the Vitest equivalent of jest.mocked?

Vitest provides vi.mocked for an already mocked import and vi.fn for a new mock function. Passing a typed implementation to vi.fn lets TypeScript infer the parameters and return shape. Choose the helper that matches whether the production import was mocked or a dependency is injected directly into the subject.

Why is as unknown as unsafe in test fixtures?

The double assertion tells TypeScript to accept a value without proving its structural compatibility with the target. A stale fixture can therefore omit a required field or retain a renamed field while compilation stays green. Use a complete factory, satisfies, or validated boundary parsing so changed production types fail tests during compilation.

Should a mock factory return Partial of the production type?

Usually no. Accept Partial of the type as an override input, merge it with complete valid defaults, and return the full production type. Returning Partial moves optionality into every consumer and can hide missing required values. Use a partial return only when the production dependency itself accepts a genuinely partial object.

Do typed mocks remove the need for runtime tests?

No. Types reject incompatible setup, but they do not prove call order, retry behavior, error handling, branching, side effects, or the realism of a chosen fixture. Keep runtime assertions for behavior and use compilation as an additional gate. Typed setup narrows the ways a test can become stale without warning.