feat: add official test utils for remote functions#15671
Open
brendan-morin wants to merge 4 commits intosveltejs:mainfrom
Open
feat: add official test utils for remote functions#15671brendan-morin wants to merge 4 commits intosveltejs:mainfrom
brendan-morin wants to merge 4 commits intosveltejs:mainfrom
Conversation
added 4 commits
April 7, 2026 13:21
Adds official test utilities for unit testing remote functions without mocking SvelteKit internals. - createTestEvent(options) builds a mock RequestEvent with sensible defaults - withRequestContext(event, fn) establishes the request store context using with_request_store - callRemote(fn, arg, options) auto-detects function type (query/command/form) and sets the appropriate HTTP method - setLocals(locals) modifies the current test's request context - HttpValidationError extends HttpError to surface Standard Schema validation issues for test assertions - svelteKitTest() Vitest plugin resolves virtual modules, transforms .remote.ts files, and injects auto-context per test via als.enterWith()
Introduces component testing: mockRemote(fn) controls what data
components receive when rendering remote functions, without executing
server logic.
The svelteKitTest plugin gains a mode option. In component mode, it
redirects .remote.ts imports to virtual module IDs via resolveId + load,
bypassing the production sveltekit() plugin's transform. The load hook
reads the original source, parses exports via regex, and generates
client stubs pointing to a mock runtime.
The mock runtime provides MockQueryProxy with $state-backed reactive
properties, mock commands with .pending tracking, and mock forms with
a recursive Proxy for nested field access.
mockRemote API is chainable:
mockRemote(fn).returns(data)
mockRemote(fn).withFieldValues({ email: 'alice@example.com' })
mockRemote(fn).withFieldIssues({ name: [{ message: 'Required' }] })
Covers server-side testing (auto-context, setLocals, callRemote, withRequestContext, validation errors) and component testing (mockRemote with queries, commands, and forms). Includes Svelte component + test file pairs showing the full pattern. Covers dual-mode Vitest project configuration for projects that need both server and component tests.
🦋 Changeset detectedLatest commit: 051f676 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #14796
Summary
Add official test utilities for unit testing remote functions and components that use them. The target here is a lightweight and intuitive DX for general remote function testing that is sufficiently flexible. This has the added benefit for being groundwork for additional testing utilities as SvelteKit continue to grow.
Quick Examples
The easiest way to understand the intent of this PR is to consider the following examples (the included docs also are a great place to start):
Unit Testing Remote Functions
Current State
Naive testing of remote functions fails:
The primary workaround is mocking complicated $app/server internals:
After this PR
When using the svelteKitTest plugin, testing remote functions "just works"
Testing Components with Remote Functions
Current State
Given a component that uses a remote form:
There is no standard way to unit test this component. The best pattern I've seen for this is the functional core/imperative shell pattern, which still requires manually mocking internal RF functionality. Or jumping into full end-to-end testing via e.g. Playwright.
After this PR
We can now easily mock state for remote functions for use in component tests via a standard interface:
What's Included
@sveltejs/kit/testexports:createTestEvent(options)— mock RequestEvent with sensible defaultswithRequestContext(event, fn)— establish request store context for a callbackcallRemote(fn, arg, options)— convenience wrapper with typed overloads, auto-detects GET/POST from function typesetLocals(locals)— modify event.locals on the current test contextmockRemote(fn)— chainable builder: .returns(), .throws(), .resolves(), .withFieldValues(), .withFieldIssues()HttpValidationError— HttpError subclass with typed .issues for schema validation assertionscreateTestState(options)— shared RequestState construction@sveltejs/kit/test/vitestexports:svelteKitTest(options?)— Vitest plugin with two modes:als.enterWith()Documentation to demonstrate basic usage
Key Design Decisions
I made a judgement call on a few things here, but it's entirely possible there are more idiomatic ways to go about this, so I'm open to any feedback on these.
Auto-context uses
als.enterWith(), notsync_store. Settingsync_storedirectly doesn't survive nestedwith_request_storecalls (the finally block resets it).enterWithsets a persistent ALS context that survives becauseAsyncLocalStoragemaintains a context stack. The dev server uses the same mechanism, which was the inspiration here.__test_set_request_storeand__test_clear_request_storeare exported from@sveltejs/kit/internal/server. Thealsinstance inevent.jsis module-private — there's no way to callenterWithon it without exporting a function. These are technically part of the public API (event.js), but my hope was the__test_prefix signals test infrastructure use. This was purely additive, no modifications to existing code.handleValidationErrorthrowsHttpValidationErrordirectly. In production,handleValidationErrorreturns{ message: 'Bad Request' }and issues are only logged to console. In tests, our handler throwsHttpValidationError(which extendsHttpError), short-circuiting the framework'serror(400, ...)call. Because this is a test util, the goal was to give test consumers easy typed access to.issuesfor any assertions.Component mode uses virtual module redirect to coexist with
sveltekit(). The production plugin checks file paths for.remote.ts—enforce: 'pre'alone doesn't prevent it from also transforming the file. OurresolveIdhook redirects.remote.tsimports to virtual IDs (\0sveltekit-test-mock:{hash}) that don't match the production plugin's pattern.Test plan
This should be fairly comprehensively tested. I'm a believer in test-as-documentation as well, so if any tests are unclear please let me know.
pnpm run format/pnpm run lint/pnpm run check/pnpm -F @sveltejs/kit test:unit@sveltejs/kit/testand@sveltejs/kit/test/vitestOrganization
I tried splitting this into a couple commits (server testing, component testing, docs) to hopefully make it easier to review. I'm happy to break this down further as needed.
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm testand lint the project withpnpm lintandpnpm checkChangesets
pnpm changesetand following the prompts. Changesets that add features should beminorand those that fix bugs should bepatch. Please prefix changeset messages withfeat:,fix:, orchore:.Edits