callRoute
Call a route handler or a page action in your test process, with any method and body, and read the answer like a fetch Response.
import { callRoute } from '@gio.js/core/testing';
const res = await callRoute('/api/notes', { method: 'POST', body: { text: 'hi' } });Reference
callRoute(path, options?). It takes every option of renderPage (appDir, headers, cookies, query, locale), plus:
| Option | Type | Default | Description |
|---|---|---|---|
method | string | 'GET' | Any HTTP method; upper-cased for you. |
body | string | Uint8Array | URLSearchParams | object | unknown[] | null | - | How it is sent, unless headers sets a content-type: a string as text/plain;charset=UTF-8; URLSearchParams as application/x-www-form-urlencoded; a Uint8Array as raw bytes with no content type; any other value as JSON with application/json. |
Returns
A Promise<RouteResponse>:
| Field | Type | Default | Description |
|---|---|---|---|
status | number | - | The response status. |
headers | Record<string, string> | - | Response headers, lowercase - without the ones the Rust server adds. |
setCookies | string[] | - | Every Set-Cookie value, in order. |
text() | Promise<string> | - | The body as text. For an event stream, it resolves once the handler closes the stream. |
json() | Promise<T> | - | The body parsed as JSON. |
bytes() | Promise<Uint8Array> | - | The raw body. |
stream | ReadableStream<Uint8Array> | null | - | Event streams only: the events as a client receives them. null otherwise. |
error | { message, digest?, stack? } | undefined | - | Set when a page render failed and no error.tsx answered. |
Behavior
The request goes through the same code as on the server, so the answers match:
- A returned value is JSON
200;nullorundefinedis204; aResponsepasses through. notFound()is a JSON404; a throw is a JSON500with adigest; a method the file does not export is405.req.json()on a non-JSON body is415; a broken form body is400.- A
POSTto a page runs itsaction: you get the redirect, or the re-rendered page as HTML. - A
GETto a page renders it, likerenderPage.
Examples
JSON and form bodies
Against the /api/notes handler from Request body errors, which accepts JSON and a form:
import { expect, it } from 'vitest';
import { callRoute } from '@gio.js/core/testing';
it('accepts JSON and form posts', async () => {
const json = await callRoute('/api/notes', { method: 'POST', body: { text: 'hi' } });
expect(json.status).toBe(201);
expect(await json.json()).toEqual({ saved: 'hi' });
const form = await callRoute('/api/notes', { method: 'POST', body: new URLSearchParams({ text: 'form' }) });
expect(form.status).toBe(201);
});
it('refuses a text/plain body', async () => {
const res = await callRoute('/api/notes', { method: 'POST', body: 'plain' });
expect(res.status).toBe(415);
});A page action
Against the contact page from redirect, whose action redirects with a session cookie or re-renders with a 422:
it('redirects after a valid contact form', async () => {
const res = await callRoute('/contact', {
method: 'POST',
body: new URLSearchParams({ email: '[email protected]' }),
});
expect(res.status).toBe(303);
expect(res.headers.location).toBe('/contact/thanks');
expect(res.setCookies[0]).toMatch(/^gio_session=/);
});
it('re-renders with a 422 for an invalid email', async () => {
const res = await callRoute('/contact', { method: 'POST', body: new URLSearchParams({ email: 'nope' }) });
expect(res.status).toBe(422);
expect(await res.text()).toContain('Enter a valid email address.');
});Event streams
For a handler that returns a GioEventStream, read stream piece by piece, or text() for a stream the handler closes. Cancelling the reader runs the handler's cleanup function, like a client disconnecting. Here /api/clock is the handler from GioEventStream.
it('ticks', async () => {
const res = await callRoute('/api/clock');
expect(res.headers['content-type']).toBe('text/event-stream');
const reader = res.stream!.getReader();
const { value } = await reader.read();
expect(new TextDecoder().decode(value)).toContain('event: tick');
await reader.cancel(); // runs the cleanup
});Good to know
- No Rust in between. CSRF checks, rules, guards, rate limits and
[server] max_body_bytesare not applied - so a cross-sitePOSTsucceeds here. Use createTestServer to test them. - No cache, no WebSocket server.
revalidateTagandrevalidatePathresolveok: false, andbroadcastreturnsfalse. - Sessions work. Without a configured secret, a random
GIO_SESSION_SECRETis set for the test process; pass the cookie back withcookiesto act as the signed-in user. - A
route.tsthat throws on import answers500for every method, as on the server.
Related
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Introduced. |