lightsoutAlpha

test-mock-untyped

a `jest.fn()` with no generic, so the spy does not match the real signature

Deterministic checkblocking by defaultbasetests

The argument

Mock Typing Rules

Every jest.fn() must be fully typed to the real function's signature — read the source first.

// ✅ generic matches the real signature (async: include the Promise wrapper)
const mockGetProfile = jest.fn<(params: { userId: string }) => Profile | null>();

// ✅ factory wrapper uses typed parameters — never (...args: unknown[]) (causes TS2556)
jest.mock('@/utils/get-profile', () => ({
	getProfile: (params: { userId: string }) => mockGetProfile(params),
}));

Using () => mockFn() for a function that takes parameters silently discards arguments — the spy records zero-arg calls and toHaveBeenCalledWith fails. Some existing files use (...args: unknown[]) — that is legacy debt; new tests always type the wrapper.

Framework-generic results are exempt. These typing rules pin your contracts, not the framework's. When a stub must satisfy a framework's heavily generic result type (TanStack's UseMutationResult / UseQueryResult and kin), stub only the fields the unit under test reads and cast loosely (as Record<string, unknown>, or as unknown as UseMutationResult<…> where the full type is demanded) — reproducing the framework's generics in a stub adds noise, not safety.

The proof

failsubject.unit.test.ts
import { expect, describe, test, jest } from '@jest/globals';

const mockGetLocale = jest.fn();

describe('subject', () => {
	test('reads the locale', () => {
		mockGetLocale.mockReturnValue('en-GB');

		expect(mockGetLocale()).toBe('en-GB');
	});
});
passsubject.unit.test.ts
import { expect, describe, test, jest } from '@jest/globals';

const mockGetLocale = jest.fn<() => string>();

const mockQueryResult = {
	data: 'p.png',
	isLoading: false,
	refetch: jest.fn(),
} as unknown as Record<string, unknown>;

// A test whose subject reads code as text passes that code in as data. This is
// a mention of an untyped spy, not one — the rule reads what the file does, not
// what it quotes.
const sampleLine = 'const getAvatar = jest.fn();';

describe('subject', () => {
	test('reads the locale and the stubbed field', () => {
		mockGetLocale.mockReturnValue('en-GB');

		expect(mockGetLocale() + mockQueryResult.data).toBe('en-GBp.png');
	});

	test('carries its sample line untouched', () => {
		expect(sampleLine).toContain('getAvatar');
	});
});

Turn it down

Both lines go in your lightsout.config.json.

"standards-checks": { "test-mock-untyped": "advisory" }
"standards-checks": { "test-mock-untyped": "off" }