Skip to content

test(query-core/utils): add tests for 'ensureQueryFn' initialPromise fallback and skipToken handling#10527

Open
KimHyeongRae0 wants to merge 3 commits intoTanStack:mainfrom
KimHyeongRae0:test/query-core-utils-ensure-query-fn
Open

test(query-core/utils): add tests for 'ensureQueryFn' initialPromise fallback and skipToken handling#10527
KimHyeongRae0 wants to merge 3 commits intoTanStack:mainfrom
KimHyeongRae0:test/query-core-utils-ensure-query-fn

Conversation

@KimHyeongRae0
Copy link
Copy Markdown

@KimHyeongRae0 KimHyeongRae0 commented Apr 20, 2026

🎯 Changes

Adds unit tests for ensureQueryFn in packages/query-core/src/utils.ts, covering branches that previously had no direct coverage:

  • Returns a function resolving to fetchOptions.initialPromise when queryFn is missing but an initialPromise is provided (utils.ts:450-451).
  • Propagates the rejection when the provided initialPromise rejects.
  • Logs a dev-mode console.error and returns a function that rejects with the "Missing queryFn" error when queryFn is set to skipToken (utils.ts:439-444, 454-457).

Coverage for packages/query-core/src/utils.ts improves from 96.4% → 97.6% branch and 96.77% → 100% function.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with npx nx run @tanstack/query-core:test:lib and npx nx run @tanstack/query-core:test:types.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Tests
    • Added comprehensive tests for query-resolution behavior covering: (1) resolving from an initial promise, (2) propagating promise rejections, and (3) handling a skipped query path which logs an error and surfaces a missing-query rejection.

Note: No user-facing changes; updates are limited to internal test coverage.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33ca1334-052d-4844-acd1-b414fc83ad4e

📥 Commits

Reviewing files that changed from the base of the PR and between b881482 and 902e5b9.

📒 Files selected for processing (1)
  • packages/query-core/src/__tests__/utils.test.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/query-core/src/tests/utils.test.tsx

📝 Walkthrough

Walkthrough

Adds a new test suite for the ensureQueryFn utility that verifies behavior when queryFn is absent but an initialPromise is provided, when initialPromise rejects, and when queryFn is skipToken (expects error logging and a rejection keyed by queryHash).

Changes

Cohort / File(s) Summary
ensureQueryFn Test Suite
packages/query-core/src/__tests__/utils.test.tsx
New tests added for ensureQueryFn covering: fallback to initialPromise resolution, propagation of initialPromise rejection, and handling of skipToken with console error and missing-queryFn rejection keyed by queryHash.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐇 I hopped through tests with curious cheer,
Ensuring queries behave when funcs disappear.
Promises kept faithful, rejections called true,
SkipToken announced — a console boo-hoo,
Tiny paws stamp tests, the bedrock grew.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: adding tests for 'ensureQueryFn' with focus on initialPromise fallback and skipToken handling, matching the raw summary content.
Description check ✅ Passed The description follows the template with all required sections completed: Changes section with detailed coverage information, Checklist with both items marked appropriately, and Release Impact with docs/CI/dev-only marked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@KimHyeongRae0 KimHyeongRae0 force-pushed the test/query-core-utils-ensure-query-fn branch from 8cdebe3 to b881482 Compare April 20, 2026 13:19
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/query-core/src/__tests__/utils.test.tsx (2)

540-588: Unnecessary as unknown as () => Promise<...> casts.

ensureQueryFn returns a QueryFunction, which is directly callable; the double cast to () => Promise<T> just to invoke it is noisy and hides the real signature. Passing a minimal QueryFunctionContext (or typing resolved as () => Promise<unknown> once) reads cleaner and still exercises the branch. Not blocking — the tests are correct as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/__tests__/utils.test.tsx` around lines 540 - 588, The
tests use noisy double casts like (resolved as unknown as () => Promise<...>)()
— change them to call the returned QueryFunction directly by either typing
resolved as the appropriate function type or invoking it with a minimal
QueryFunctionContext; specifically update uses of resolved from ensureQueryFn
(and the skipToken case) to remove the "as unknown as" casts and instead call
resolved(...) with a minimal context or declare resolved: QueryFunction/() =>
Promise<...> so the real signature is exercised and the casts are eliminated.

567-587: Restore console.error spy in a finally / afterEach to prevent leakage on assertion failure.

If either expect above mockRestore() throws, the spy is never restored and will bleed into subsequent tests in the suite (e.g. polluting other console.error assertions or hiding real errors). Prefer afterEach(() => vi.restoreAllMocks()) at the describe('ensureQueryFn') level, or wrap in try { ... } finally { consoleErrorSpy.mockRestore() }.

♻️ Suggested structure
     it('should return a function that rejects with missing queryFn error when queryFn is set to skipToken', async () => {
       const consoleErrorSpy = vi
         .spyOn(console, 'error')
         .mockImplementation(() => undefined)

-      const resolved = ensureQueryFn({
-        queryFn: skipToken,
-        queryHash: '["skip"]',
-      })
-
-      expect(consoleErrorSpy).toHaveBeenCalledWith(
-        expect.stringContaining(
-          'Attempted to invoke queryFn when set to skipToken',
-        ),
-      )
-      await expect(
-        (resolved as unknown as () => Promise<unknown>)(),
-      ).rejects.toThrow('Missing queryFn: \'["skip"]\'')
-
-      consoleErrorSpy.mockRestore()
+      try {
+        const resolved = ensureQueryFn({
+          queryFn: skipToken,
+          queryHash: '["skip"]',
+        })
+
+        expect(consoleErrorSpy).toHaveBeenCalledWith(
+          expect.stringContaining(
+            'Attempted to invoke queryFn when set to skipToken',
+          ),
+        )
+        await expect(
+          (resolved as unknown as () => Promise<unknown>)(),
+        ).rejects.toThrow('Missing queryFn: \'["skip"]\'')
+      } finally {
+        consoleErrorSpy.mockRestore()
+      }
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/__tests__/utils.test.tsx` around lines 567 - 587, The
test creates a spy on console.error (consoleErrorSpy) but restores it only at
the end of the test, which can leak if an assertion throws; update the test
suite that contains the ensureQueryFn tests to guarantee the spy is always
restored by either adding an afterEach(() => vi.restoreAllMocks()) scoped to the
describe block for ensureQueryFn or wrapping the test body in try { ... }
finally { consoleErrorSpy.mockRestore() }; reference ensureQueryFn, skipToken,
and consoleErrorSpy when making the change so the spy is always cleaned up even
on assertion failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/query-core/src/__tests__/utils.test.tsx`:
- Around line 540-588: The tests use noisy double casts like (resolved as
unknown as () => Promise<...>)() — change them to call the returned
QueryFunction directly by either typing resolved as the appropriate function
type or invoking it with a minimal QueryFunctionContext; specifically update
uses of resolved from ensureQueryFn (and the skipToken case) to remove the "as
unknown as" casts and instead call resolved(...) with a minimal context or
declare resolved: QueryFunction/() => Promise<...> so the real signature is
exercised and the casts are eliminated.
- Around line 567-587: The test creates a spy on console.error (consoleErrorSpy)
but restores it only at the end of the test, which can leak if an assertion
throws; update the test suite that contains the ensureQueryFn tests to guarantee
the spy is always restored by either adding an afterEach(() =>
vi.restoreAllMocks()) scoped to the describe block for ensureQueryFn or wrapping
the test body in try { ... } finally { consoleErrorSpy.mockRestore() };
reference ensureQueryFn, skipToken, and consoleErrorSpy when making the change
so the spy is always cleaned up even on assertion failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ed94f86-9b94-43af-ba5b-88d745b565ad

📥 Commits

Reviewing files that changed from the base of the PR and between 799952f and b881482.

📒 Files selected for processing (1)
  • packages/query-core/src/__tests__/utils.test.tsx

@KimHyeongRae0
Copy link
Copy Markdown
Author

KimHyeongRae0 commented Apr 21, 2026

Leaving the spy/cast style as-is. Matches the existing pattern in this file.

Copy link
Copy Markdown
Collaborator

@sukvvon sukvvon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The triple (resolved as unknown as () => Promise<...>)() cast can be avoided by calling the returned function with a QueryFunctionContext. Since the implementation ignores the context, {} as QueryFunctionContext is enough, and it stays type-safe.

shouldThrowError,
skipToken,
} from '../utils'
import { Mutation } from '../mutation'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { Mutation } from '../mutation'
import { Mutation } from '../mutation'
import type { QueryFunctionContext } from '..'

Comment on lines +548 to +550
await expect(
(resolved as unknown as () => Promise<string>)(),
).resolves.toBe('initial-data')
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await expect(
(resolved as unknown as () => Promise<string>)(),
).resolves.toBe('initial-data')
await expect(resolved(context)).resolves.toBe('initial-data')

Also add a shared context at the top of the describe block:

describe('ensureQueryFn', () => {
  const context = {} as QueryFunctionContext
  ...

Comment on lines +562 to +564
await expect(
(resolved as unknown as () => Promise<unknown>)(),
).rejects.toBe(error)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await expect(
(resolved as unknown as () => Promise<unknown>)(),
).rejects.toBe(error)
await expect(resolved(context)).rejects.toBe(error)

Comment on lines +582 to +584
await expect(
(resolved as unknown as () => Promise<unknown>)(),
).rejects.toThrow('Missing queryFn: \'["skip"]\'')
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await expect(
(resolved as unknown as () => Promise<unknown>)(),
).rejects.toThrow('Missing queryFn: \'["skip"]\'')
await expect(resolved(context)).rejects.toThrow(
'Missing queryFn: \'["skip"]\'',
)

…Fn tests

Replace the `(resolved as unknown as () => Promise<...>)()` double-cast
with `resolved(context)` using a shared `QueryFunctionContext` fixture.
Per review suggestions on TanStack#10527.
@KimHyeongRae0
Copy link
Copy Markdown
Author

Fair point. Applied all four in 902e5b9 — context fixture is cleaner than the double cast.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Apr 23, 2026

View your CI Pipeline Execution ↗ for commit 6763bff

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 39s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-23 17:19:11 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 23, 2026

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@10527

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@10527

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10527

@tanstack/preact-query-devtools

npm i https://pkg.pr.new/@tanstack/preact-query-devtools@10527

@tanstack/preact-query-persist-client

npm i https://pkg.pr.new/@tanstack/preact-query-persist-client@10527

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@10527

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@10527

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@10527

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@10527

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@10527

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@10527

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@10527

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@10527

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@10527

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@10527

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@10527

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@10527

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@10527

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@10527

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@10527

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@10527

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@10527

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@10527

commit: 6763bff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants