feat: add official test utils for remote functions#15671
feat: add official test utils for remote functions#15671brendan-morin wants to merge 9 commits intosveltejs:mainfrom
Conversation
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: 020d817 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 |
|
@spences10 would love to get your perspective here, since you've put a lot of good work into evangelizing svelte testing patterns with sveltest |
|
Thanks for the mention @brendan-morin! If this lands, I'd love to update sveltest to use these utilities and document the patterns around them. |
|
|
||
| // Component rendering tests fail on Node 18 with lifecycle_function_unavailable | ||
| // "mount(...) is not available on the server" | ||
| describe.skip('form component rendering', () => { |
There was a problem hiding this comment.
I'm not sure what the recommended process for this handling this, but basically this test fails the node 18 checks in CI but passes everything else. As far as I can tell it's related to trying to do rendering in this test.
Having skipped tests here isn't a long term goal, but is there anything about node 18 or its deps that is special that we'd expect it to fail here where the other versions don't have problems (maybe a vitest-browser-svelte compatibility issue)?
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