Skip to content

fix(jest-mock): walk overloads in ResolveType/RejectType#16237

Open
tsushanth wants to merge 5 commits into
jestjs:mainfrom
tsushanth:fix/mock-overload-resolve-reject
Open

fix(jest-mock): walk overloads in ResolveType/RejectType#16237
tsushanth wants to merge 5 commits into
jestjs:mainfrom
tsushanth:fix/mock-overload-resolve-reject

Conversation

@tsushanth

Copy link
Copy Markdown

Closes #16174.

Summary

ReturnType<T> picks the last overload signature of T. For functions like pg.Client['end'] whose overloads are (): Promise<void> and (cb): void, ReturnType collapses to void — the Promise-shaped overload disappears, ResolveType / RejectType both infer never, and mockResolvedValue / mockRejectedValue reject any value with TS2345: Argument of type Error is not assignable to parameter of type never.

Fix

Introduce OverloadedReturnType<T> that walks up to four call signatures and unions their return types. Then keep the existing PromiseLike check through Extract<..., PromiseLike<any>> so:

  • Sync overloads disappear from the resolve-type pool — mockResolvedValue on a sync overload still infers never.
  • The Promise-shaped overload survives — mockResolvedValue infers the resolved U, mockRejectedValue infers unknown (matching the historical contract).
type OverloadedReturnType<T> = T extends {
    (...args: any[]): infer R1;
    (...args: any[]): infer R2;
    (...args: any[]): infer R3;
    (...args: any[]): infer R4;
  } ? R1 | R2 | R3 | R4
  : T extends { (...args: any[]): infer R1; (...args: any[]): infer R2; (...args: any[]): infer R3 } ? R1 | R2 | R3
  : T extends { (...args: any[]): infer R1; (...args: any[]): infer R2 } ? R1 | R2
  : T extends (...args: any[]) => infer R ? R
  : never;

type ResolveType<T extends FunctionLike> =
  Extract<OverloadedReturnType<T>, PromiseLike<any>> extends PromiseLike<infer U> ? U : never;

type RejectType<T extends FunctionLike> =
  Extract<OverloadedReturnType<T>, PromiseLike<any>> extends PromiseLike<any> ? unknown : never;

Four overload arities should cover the long tail — pg.Client['end'] has 2, most DOM/Node APIs cap at 3-4. If anyone needs more, the pattern extends mechanically.

Test plan

Added one typetest in packages/jest-mock/__typetests__/mock-functions.test.ts:

interface OverloadedEnd {
  (): Promise<void>;
  (callback: (err: Error) => void): void;
}
const overloaded = fn<OverloadedEnd>();
expect(overloaded.mockResolvedValue).type.toBeCallableWith(undefined);
expect(overloaded.mockRejectedValue).type.toBeCallableWith(new Error('boom'));

The existing typetests at lines 78-90 and 479-531 (single-signature sync vs async functions) continue to assert the same contract — sync-only functions still reject any resolve/reject value, async functions still narrow the resolve type to the resolved U.

I wasn't able to run yarn build:js / yarn test-types locally (disk space exhausted on my machine). CI should cover typecheck:tests and the tstyche suite — happy to amend if anything surfaces.

Closes jestjs#16174.

`ReturnType<T>` picks the LAST overload signature of `T`. For functions
like `pg.Client['end']` whose overloads are `(): Promise<void>` and
`(cb): void`, `ReturnType` collapses to `void` and the Promise-shaped
overload disappears — `mockResolvedValue` / `mockRejectedValue` then
infer `never` and reject any value (`TS2345: Argument of type Error is
not assignable to parameter of type never`).

Introduce `OverloadedReturnType<T>` that walks up to four call signatures
and unions their return types, then keep the existing PromiseLike check
through `Extract<..., PromiseLike<any>>` so the Promise-shaped overload
survives even when a later non-Promise overload exists.

Existing typetests covering single-signature functions (sync and async)
remain unchanged; a new regression test pins the overload behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 10, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: tsushanth / name: tsushanth (a8e9e28)

@netlify

netlify Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploy Preview for jestjs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 89c792e
🔍 Latest deploy log https://app.netlify.com/projects/jestjs/deploys/6a2d570aa8dbf50008d3f5fd
😎 Deploy Preview https://deploy-preview-16237--jestjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the require-changelog If a PR does requires a changelog entry label Jun 10, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

babel-jest

npm i https://pkg.pr.new/babel-jest@16237

babel-plugin-jest-hoist

npm i https://pkg.pr.new/babel-plugin-jest-hoist@16237

babel-preset-jest

npm i https://pkg.pr.new/babel-preset-jest@16237

create-jest

npm i https://pkg.pr.new/create-jest@16237

@jest/diff-sequences

npm i https://pkg.pr.new/@jest/diff-sequences@16237

expect

npm i https://pkg.pr.new/expect@16237

@jest/expect-utils

npm i https://pkg.pr.new/@jest/expect-utils@16237

jest

npm i https://pkg.pr.new/jest@16237

jest-changed-files

npm i https://pkg.pr.new/jest-changed-files@16237

jest-circus

npm i https://pkg.pr.new/jest-circus@16237

jest-cli

npm i https://pkg.pr.new/jest-cli@16237

jest-config

npm i https://pkg.pr.new/jest-config@16237

@jest/console

npm i https://pkg.pr.new/@jest/console@16237

@jest/core

npm i https://pkg.pr.new/@jest/core@16237

@jest/create-cache-key-function

npm i https://pkg.pr.new/@jest/create-cache-key-function@16237

jest-diff

npm i https://pkg.pr.new/jest-diff@16237

jest-docblock

npm i https://pkg.pr.new/jest-docblock@16237

jest-each

npm i https://pkg.pr.new/jest-each@16237

@jest/environment

npm i https://pkg.pr.new/@jest/environment@16237

jest-environment-jsdom

npm i https://pkg.pr.new/jest-environment-jsdom@16237

@jest/environment-jsdom-abstract

npm i https://pkg.pr.new/@jest/environment-jsdom-abstract@16237

jest-environment-node

npm i https://pkg.pr.new/jest-environment-node@16237

@jest/expect

npm i https://pkg.pr.new/@jest/expect@16237

@jest/fake-timers

npm i https://pkg.pr.new/@jest/fake-timers@16237

@jest/get-type

npm i https://pkg.pr.new/@jest/get-type@16237

@jest/globals

npm i https://pkg.pr.new/@jest/globals@16237

jest-haste-map

npm i https://pkg.pr.new/jest-haste-map@16237

jest-jasmine2

npm i https://pkg.pr.new/jest-jasmine2@16237

jest-leak-detector

npm i https://pkg.pr.new/jest-leak-detector@16237

jest-matcher-utils

npm i https://pkg.pr.new/jest-matcher-utils@16237

jest-message-util

npm i https://pkg.pr.new/jest-message-util@16237

jest-mock

npm i https://pkg.pr.new/jest-mock@16237

@jest/pattern

npm i https://pkg.pr.new/@jest/pattern@16237

jest-phabricator

npm i https://pkg.pr.new/jest-phabricator@16237

jest-regex-util

npm i https://pkg.pr.new/jest-regex-util@16237

@jest/reporters

npm i https://pkg.pr.new/@jest/reporters@16237

jest-resolve

npm i https://pkg.pr.new/jest-resolve@16237

jest-resolve-dependencies

npm i https://pkg.pr.new/jest-resolve-dependencies@16237

jest-runner

npm i https://pkg.pr.new/jest-runner@16237

jest-runtime

npm i https://pkg.pr.new/jest-runtime@16237

@jest/schemas

npm i https://pkg.pr.new/@jest/schemas@16237

jest-snapshot

npm i https://pkg.pr.new/jest-snapshot@16237

@jest/snapshot-utils

npm i https://pkg.pr.new/@jest/snapshot-utils@16237

@jest/source-map

npm i https://pkg.pr.new/@jest/source-map@16237

@jest/test-result

npm i https://pkg.pr.new/@jest/test-result@16237

@jest/test-sequencer

npm i https://pkg.pr.new/@jest/test-sequencer@16237

@jest/transform

npm i https://pkg.pr.new/@jest/transform@16237

@jest/types

npm i https://pkg.pr.new/@jest/types@16237

jest-util

npm i https://pkg.pr.new/jest-util@16237

jest-validate

npm i https://pkg.pr.new/jest-validate@16237

jest-watcher

npm i https://pkg.pr.new/jest-watcher@16237

jest-worker

npm i https://pkg.pr.new/jest-worker@16237

pretty-format

npm i https://pkg.pr.new/pretty-format@16237

commit: 89c792e

Lint failure on the original commit: @typescript-eslint/array-type forbids
any[] in source. Use Array<any> instead, plus break the long
Extract<...> extends PromiseLike<infer U> line per prettier (162 chars).
No behavioural change to OverloadedReturnType / ResolveType / RejectType.
Previous version used Extract<...> extends PromiseLike<infer U>, which
distributes over never: when the function's return type isn't a
PromiseLike at all, Extract collapses to never, and never extends
PromiseLike<infer U> resolves with infer U = unknown, not never. That
made mockResolvedValue / mockRejectedValue callable on plain non-Promise
functions like () => 'value' and broke the __typetests__ assertions
that the pre-fix behaviour kept uncallable.

Wrap the Extract result in [...] to short-circuit the never case
explicitly: [Extract<..., PromiseLike<any>>] extends [never] ? never :
...continues with the PromiseLike inference. Single-signature
non-Promise functions now resolve to never as before, and the overload
walking still works for the pg.Client-style cases this PR fixes.
Hand-formatting of the [Extract<...>] extends [never] guard didn't
match prettier's preferred multi-line tuple shape. Apply
`prettier --write` to the file so the lint check is satisfied. No
semantic change.
Local re-format used a newer prettier version that wraps interface
extends differently from the repo-locked 3.8.3 in yarn.lock. Run
`prettier --write` with the pinned version + the project's
package.json config so the lint step is clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

require-changelog If a PR does requires a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Spurious type error when calling mockRejectedValue on mocks of certain overloaded functions

1 participant