11---
22description : |
3- Add a global app wrapper to provide common meta tags or context for application routes .
3+ Learn how to test Fresh applications using Deno's built-in test runner .
44---
55
66To ensure that your application works as expected we can write tests. Any aspect
@@ -11,21 +11,25 @@ write tests.
1111## Testing middlewares
1212
1313To test [ middlewares] ( /docs/concepts/middleware ) we're going to create a dummy
14- app and return the relevant info we want to check in a custom ` / ` handler.
14+ app and return the relevant info we want to check in a custom ` / ` handler. This
15+ test assumes the ` State ` object in ` utils.ts ` has ` text ` property.
1516
16- ``` ts middleware.test.ts
17+ ``` ts tests/ middleware.test.ts
1718import { expect } from " @std/expect" ;
1819import { App } from " fresh" ;
20+ import { define , type State } from " ../utils.ts" ;
1921
2022const middleware = define .middleware ((ctx ) => {
2123 ctx .state .text = " middleware text" ;
2224 return ctx .next ();
2325});
2426
2527Deno .test (" My middleware - sets ctx.state.text" , async () => {
26- const handler = new App ()
28+ const handler = new App < State > ()
2729 .use (middleware )
28- .get (" /" , (ctx ) => new Response (ctx .state .text ))
30+ .get (" /" , (ctx ) => {
31+ return new Response (ctx .state .text || " " );
32+ })
2933 .handler ();
3034
3135 const res = await handler (new Request (" http://localhost" ));
@@ -43,11 +47,12 @@ that adds a header to the returned response, you can assert against that too.
4347Both the [ app wrapper] ( /docs/advanced/app-wrapper ) component and
4448[ layouts] ( /docs/advanced/layouts ) can be tested in the same way.
4549
46- ``` tsx routes/_app .test.tsx
50+ ``` tsx tests/appWrapper .test.tsx
4751import { expect } from " @std/expect" ;
4852import { App } from " fresh" ;
53+ import { define , type State } from " ../utils.ts" ;
4954
50- function AppWrapper({ Component }) {
55+ const AppWrapper = define . layout ( function AppWrapper({ Component }) {
5156 return (
5257 <html lang = " en" >
5358 <head >
@@ -59,10 +64,10 @@ function AppWrapper({ Component }) {
5964 </body >
6065 </html >
6166 );
62- }
67+ });
6368
6469Deno .test (" App Wrapper - renders title and content" , async () => {
65- const handler = new App ()
70+ const handler = new App < State > ()
6671 .appWrapper (AppWrapper )
6772 .get (" /" , (ctx ) => ctx .render (<h1 >hello</h1 >))
6873 .handler ();
@@ -71,71 +76,64 @@ Deno.test("App Wrapper - renders title and content", async () => {
7176 const text = await res .text ();
7277
7378 expect (text ).toContain (" My App" );
74- expect (text ).toContain (" Hello " );
79+ expect (text ).toContain (" hello " );
7580});
7681```
7782
7883Same can be done for layouts.
7984
80- ``` tsx routes/_layout .test.tsx
85+ ``` tsx tests/layout .test.tsx
8186import { expect } from " @std/expect" ;
8287import { App } from " fresh" ;
88+ import { define , type State } from " ../utils.ts" ;
8389
84- function MyLayout({ Component }) {
90+ const MyLayout = define . layout ( function MyLayout({ Component }) {
8591 return (
8692 <div >
8793 <h1 >My Layout</h1 >
8894 <Component />
8995 </div >
9096 );
91- }
97+ });
9298
9399Deno .test (" MyLayout - renders heading and content" , async () => {
94- const handler = new App ()
95- .layout ( " * " , MyLayout )
100+ const handler = new App < State > ()
101+ .appWrapper ( MyLayout )
96102 .get (" /" , (ctx ) => ctx .render (<h1 >hello</h1 >))
97103 .handler ();
98104
99105 const res = await handler (new Request (" http://localhost" ));
100106 const text = await res .text ();
101107
102108 expect (text ).toContain (" My Layout" );
103- expect (text ).toContain (" Hello " );
109+ expect (text ).toContain (" hello " );
104110});
105111```
106112
107113## Testing routes and handlers
108114
109115For testing your route handlers and business logic, you can use the same
110- [ ` App ` ] ( /docs/concepts/app ) pattern shown above. Fresh 2.0 makes it easy to test
111- individual routes without needing a full build process:
116+ [ ` App ` ] ( /docs/concepts/app ) pattern shown above. Fresh makes it easy to test
117+ individual routes without needing a full build process, as long as they export a
118+ handler:
112119
113- ``` ts my- routes.test.ts
120+ ``` ts tests/ routes.test.ts
114121import { expect } from " @std/expect" ;
115122import { App } from " fresh" ;
123+ import { type State } from " ../utils.ts" ;
116124
117- // Import your route handlers
118- import { handler as indexHandler } from " ./routes/index.ts" ;
119- import { handler as apiHandler } from " ./routes/api/users.ts" ;
125+ // Import actual route handlers
126+ import { handler as apiHandler } from " ../routes/api/[name].tsx" ;
120127
121- Deno .test (" Index route returns homepage" , async () => {
122- const app = new App ().get (" /" , indexHandler );
123- const handler = app .handler ();
128+ Deno .test (" API route returns name" , async () => {
129+ const app = new App <State >()
130+ .get (" /api/:name" , apiHandler .GET )
131+ .handler ();
124132
125- const response = await handler (new Request (" http://localhost/" ));
133+ const response = await app (new Request (" http://localhost/api/joe " ));
126134 const text = await response .text ();
127135
128- expect (text ).toContain (" Welcome" );
129- });
130-
131- Deno .test (" API route returns JSON" , async () => {
132- const app = new App ().get (" /api/users" , apiHandler );
133- const handler = app .handler ();
134-
135- const response = await handler (new Request (" http://localhost/api/users" ));
136- const json = await response .json ();
137-
138- expect (json ).toEqual ({ users: [] });
136+ expect (text ).toEqual (" Hello, Joe!" );
139137});
140138```
141139
@@ -150,28 +148,37 @@ You can test that your islands render correctly on the server using the same
150148[ ` App ` ] ( /docs/concepts/app ) pattern. Note: this requires a ` .tsx ` file extension
151149to use JSX:
152150
153- ``` tsx island-ssr.test.tsx
151+ ``` tsx tests/ island-ssr.test.tsx
154152import { expect } from " @std/expect" ;
155153import { App } from " fresh" ;
156- import Counter from " ./islands/Counter.tsx" ;
154+ import { useSignal } from " @preact/signals" ;
155+ import { type State } from " ../utils.ts" ;
156+ import Counter from " ../islands/Counter.tsx" ;
157+
158+ function CounterPage() {
159+ const count = useSignal (3 );
160+ return (
161+ <div class = " p-8" >
162+ <h1 >Counter Test Page</h1 >
163+ <Counter count = { count } />
164+ </div >
165+ );
166+ }
157167
158168Deno .test (" Counter page renders island" , async () => {
159- const app = new App ().get (" /counter" , (ctx ) => {
160- return ctx .render (
161- <div className = " p-8" >
162- <h1 >Counter Test Page</h1 >
163- <Counter />
164- </div >,
165- );
166- });
167- const handler = app .handler ();
169+ const app = new App <State >()
170+ .get (" /counter" , (ctx ) => {
171+ return ctx .render (<CounterPage />);
172+ })
173+ .handler ();
168174
169- const response = await handler (new Request (" http://localhost/counter" ));
175+ const response = await app (new Request (" http://localhost/counter" ));
170176 const html = await response .text ();
171177
172178 // Verify the island's initial HTML is present
173- expect (html ).toContain (' class="counter"' );
174- expect (html ).toContain (" count: 0" );
179+ expect (html ).toContain (' class="flex gap-8 py-6"' );
180+ expect (html ).toContain (" Counter Test Page" );
181+ expect (html ).toContain (" 3" );
175182});
176183```
177184
@@ -181,27 +188,56 @@ For testing client-side island behavior (clicks, state changes, etc.), you need
181188a full build and browser environment. You can use the approach similar to
182189Fresh's own tests:
183190
184- ``` tsx island-client.test.tsx
191+ ``` tsx tests/ island-client.test.tsx
185192import { expect } from " @std/expect" ;
186- import { createBuilder } from " vite" ;
193+ import { buildFreshApp , startTestServer } from " ./test-utils.ts" ;
194+
195+ const app = await buildFreshApp ();
196+
197+ Deno .test (" Counter island renders correctly" , async () => {
198+ const { server, address } = startTestServer (app );
199+
200+ try {
201+ // Basic smoke test: verify the island HTML is served
202+ const response = await fetch (` ${address }/ ` );
203+ const html = await response .text ();
204+
205+ expect (html ).toContain (' class="flex gap-8 py-6"' );
206+ expect (html ).toContain (" 3" );
207+ } finally {
208+ await server .shutdown ();
209+ }
210+ });
211+ ```
212+
213+ ``` tsx tests/test-utils.ts
214+ import { createBuilder , type InlineConfig } from " vite" ;
187215import * as path from " @std/path" ;
188216
189- // Create a production build
190- const builder = await createBuilder ( {
217+ // Default Fresh build configuration
218+ export const FRESH_BUILD_CONFIG : InlineConfig = {
191219 logLevel: " error" ,
192220 root: " ./" ,
193221 build: { emptyOutDir: true },
194222 environments: {
195223 ssr: { build: { outDir: path .join (" _fresh" , " server" ) } },
196224 client: { build: { outDir: path .join (" _fresh" , " client" ) } },
197225 },
198- });
199- await builder .buildApp ();
226+ };
200227
201- const app = await import (" ./_fresh/server.js" );
228+ // Helper function to create and build the Fresh app
229+ export async function buildFreshApp(config : InlineConfig = FRESH_BUILD_CONFIG ) {
230+ const builder = await createBuilder (config );
231+ await builder .buildApp ();
232+ return await import (" ../_fresh/server.js" );
233+ }
202234
203- Deno .test (" Counter island renders correctly" , async () => {
204- // Start production server
235+ // Helper function to start a test server
236+ export function startTestServer(app : {
237+ default: {
238+ fetch: (req : Request ) => Promise <Response >;
239+ };
240+ }) {
205241 const server = Deno .serve ({
206242 port: 0 ,
207243 handler: app .default .fetch ,
@@ -210,21 +246,8 @@ Deno.test("Counter island renders correctly", async () => {
210246 const { port } = server .addr as Deno .NetAddr ;
211247 const address = ` http://localhost:${port } ` ;
212248
213- try {
214- // Basic smoke test: verify the island HTML is served
215- const response = await fetch (` ${address }/counter ` );
216- const html = await response .text ();
217-
218- expect (html ).toContain (' class="counter"' );
219- expect (html ).toContain (" count: 0" );
220-
221- // For full browser interactivity testing, you would need:
222- // - Browser automation tools (Puppeteer, Playwright)
223- // - withBrowser utility from Fresh's test suite
224- } finally {
225- await server .shutdown ();
226- }
227- });
249+ return { server , address };
250+ }
228251```
229252
230253** Note:** For most applications, testing the server-side rendering is
0 commit comments