noctcore-code-quality/typed-mock-over-double-cast
An object of mocks must be typed against the real interface, not double-cast past the checker.
- Package
code-qualityv0.4.2 - Recommended preset
- not listedopt-in
- Autofix
- No
- Suggestions
- No
- Options
- Optional
- Type information
- Not needed
{ get: jest.fn() } as unknown as ConfigService tells TypeScript to stop checking. When the real
service renames get to getOrThrow, the test still mocks get, the code under test calls
getOrThrow on an object that does not have it, and the failure (if the test fails at all) is a
TypeError far from the cause. Worse, a mock that returns the wrong shape keeps compiling forever.
Type the double against the members the test uses. jest.Mocked<Pick<T, 'a' | 'b'>> (or Vitest’s
Mocked<Pick<...>>) keeps every mocked member checked against T, and satisfies Partial<T>
checks the literal without widening it.
const config = { get: jest.fn((key: string) => (key === 'METRICS_BASIC_AUTH' ? basicAuth : undefined)),} as unknown as ConfigService;const config: jest.Mocked<Pick<ConfigService, 'get'>> = { get: jest.fn((key: string) => (key === 'METRICS_BASIC_AUTH' ? basicAuth : undefined)),};What it flags
Section titled “What it flags”An object literal cast as unknown as T (or as any as T, as never as T, and the angle-bracket
forms <T><unknown>{ ... } and <T>({ ... } as unknown)) that contains a mock-function call
(jest.fn() / vi.fn() by default) anywhere inside it: as a property, nested in another object,
at the start of a chain (jest.fn().mockResolvedValue(...)) or inside a function property.
const opts = { ctx: { res: { setHeader: jest.fn() } } } as unknown as OnErrorOptions;
const host = { switchToHttp: () => ({ getResponse: () => ({ status: jest.fn() }) }),} as unknown as ArgumentsHost;const opts = { ctx: { res: { setHeader: jest.fn() } } } satisfies DeepPartial<OnErrorOptions>;
const host: jest.Mocked<Pick<ArgumentsHost, 'switchToHttp'>> = { switchToHttp: jest.fn(() => ({ getResponse: () => ({ status: jest.fn() }) })),};A mock created first and placed in the object by name counts too, shorthand ({ send }) or not
({ run: send }): the name must be bound to a mock-function call, or to a chain that starts with
one, by its initialiser or by a plain assignment (let get; beforeEach(() => { get = jest.fn(); })).
const send = vi.fn();const mailer = { send } as unknown as Mailer;
let get: jest.Mock;beforeEach(() => { get = jest.fn().mockReturnValue('smtp://localhost'); config = { get } as unknown as ConfigService;});const send = vi.fn();const mailer: Mocked<Pick<Mailer, 'send'>> = { send };
let get: jest.Mock;beforeEach(() => { get = jest.fn().mockReturnValue('smtp://localhost'); config = { get } satisfies Partial<ConfigService>;});What it does not flag
Section titled “What it does not flag”- A single cast (
as jest.Mocked<Pick<T, 'get'>>): TypeScript still checks that the two types overlap. - A double cast of an object with no mock in it (a data fixture), or of something that is not an
object literal (
existingDouble as unknown as T). as unknownon its own.- A mock the rule cannot trace to its factory call in the same file: one that arrives through a
spread (
{ ...mocks }), an import, a parameter, a destructuring or a helper’s return value ({ send: makeSend() }). - A target matching
allowTargets.
Options
Section titled “Options”| Option | Type | Default | Meaning |
|---|---|---|---|
mockFactories |
string[] |
["jest.fn", "vi.fn"] |
Callees that create a mock function, as name or obj.name. |
allowTargets |
string[] (globs) |
[] |
Cast targets that may be double-cast, matched against the type name (PrismaService, Prisma.TransactionClient) and the full type text (Parameters<Guard['canActivate']>[0]). For types too wide to Pick from. |
'noctcore-code-quality/typed-mock-over-double-cast': ['error', { // An ORM client and its transaction are too wide to Pick from. allowTargets: ['PrismaService', 'Prisma.TransactionClient'],}]// rule options: {"allowTargets":["PrismaService"]}const prisma = { user: { findMany: jest.fn() } } as unknown as PrismaService;When not to use it
Section titled “When not to use it”In a codebase without strict types in its tests, where a double cast is the norm and a typed double would need a helper type library first.
Related
Section titled “Related”no-vacuous-expect: assertions that pass for almost any implementation.