|
1 | 1 | --- |
2 | | -last_modified: 2025-09-10 |
| 2 | +last_modified: 2026-05-14 |
3 | 3 | title: "Writing tests" |
4 | 4 | description: "Learn key concepts like test setup and structure, assertions, async testing, mocking, test fixtures, and code coverage" |
5 | 5 | url: /examples/testing_tutorial/ |
@@ -375,48 +375,85 @@ Deno.test("async test example", async () => { |
375 | 375 |
|
376 | 376 | ### Testing async functions |
377 | 377 |
|
378 | | -When testing functions that return promises, you should always await the result: |
| 378 | +When testing functions that return promises, always `await` the result. When the |
| 379 | +function calls a global like `fetch` or `Deno.readTextFile`, replace it with a |
| 380 | +stub from [`@std/testing/mock`](https://jsr.io/@std/testing/doc/mock/~) rather |
| 381 | +than reassigning the global directly. The stub matches the real signature (so |
| 382 | +the test file still type-checks), and combined with a `using` declaration it |
| 383 | +restores the original when the test scope exits — so one test can't poison the |
| 384 | +next. |
379 | 385 |
|
380 | | -```ts |
381 | | -// async-function.ts |
| 386 | +```ts title="async_function.ts" |
382 | 387 | export async function fetchUserData(userId: string) { |
383 | 388 | const response = await fetch(`https://api.example.com/users/${userId}`); |
384 | 389 | if (!response.ok) { |
385 | 390 | throw new Error(`Failed to fetch user: ${response.status}`); |
386 | 391 | } |
387 | 392 | return await response.json(); |
388 | 393 | } |
| 394 | +``` |
389 | 395 |
|
390 | | -// async-function_test.ts |
| 396 | +```ts title="async_function_test.ts" |
391 | 397 | import { assertEquals, assertRejects } from "jsr:@std/assert"; |
392 | | -import { fetchUserData } from "./async-function.ts"; |
| 398 | +import { stub } from "jsr:@std/testing/mock"; |
| 399 | +import { fetchUserData } from "./async_function.ts"; |
393 | 400 |
|
394 | 401 | Deno.test("fetchUserData success", async () => { |
395 | | - // Mock the fetch function for testing |
396 | | - globalThis.fetch = async (url: string) => { |
397 | | - const data = JSON.stringify({ id: "123", name: "Test User" }); |
398 | | - return new Response(data, { status: 200 }); |
399 | | - }; |
| 402 | + using _fetchStub = stub( |
| 403 | + globalThis, |
| 404 | + "fetch", |
| 405 | + () => |
| 406 | + Promise.resolve( |
| 407 | + new Response( |
| 408 | + JSON.stringify({ id: "123", name: "Test User" }), |
| 409 | + { status: 200 }, |
| 410 | + ), |
| 411 | + ), |
| 412 | + ); |
400 | 413 |
|
401 | 414 | const userData = await fetchUserData("123"); |
402 | 415 | assertEquals(userData.id, "123"); |
403 | 416 | assertEquals(userData.name, "Test User"); |
404 | 417 | }); |
405 | 418 |
|
406 | 419 | Deno.test("fetchUserData failure", async () => { |
407 | | - // Mock the fetch function to simulate an error |
408 | | - globalThis.fetch = async (url: string) => { |
409 | | - return new Response("Not Found", { status: 404 }); |
410 | | - }; |
| 420 | + using _fetchStub = stub( |
| 421 | + globalThis, |
| 422 | + "fetch", |
| 423 | + () => Promise.resolve(new Response("Not Found", { status: 404 })), |
| 424 | + ); |
411 | 425 |
|
412 | 426 | await assertRejects( |
413 | | - async () => await fetchUserData("nonexistent"), |
| 427 | + () => fetchUserData("nonexistent"), |
414 | 428 | Error, |
415 | 429 | "Failed to fetch user: 404", |
416 | 430 | ); |
417 | 431 | }); |
418 | 432 | ``` |
419 | 433 |
|
| 434 | +Run the file with `deno test async_function_test.ts`: |
| 435 | + |
| 436 | +```console |
| 437 | +running 2 tests from ./async_function_test.ts |
| 438 | +fetchUserData success ... ok (1ms) |
| 439 | +fetchUserData failure ... ok (0ms) |
| 440 | + |
| 441 | +ok | 2 passed | 0 failed (5ms) |
| 442 | +``` |
| 443 | + |
| 444 | +:::tip |
| 445 | + |
| 446 | +Why not just write `globalThis.fetch = async (url: string) => …`? Two reasons. |
| 447 | +First, the real `fetch` accepts `URL | RequestInfo`, not just `string`, so the |
| 448 | +assignment fails type-checking. Second, a plain assignment is never undone: any |
| 449 | +later test in the same file (or any code your test imports transitively) will |
| 450 | +keep seeing the mock. The `using` + `stub` pattern fixes both. |
| 451 | + |
| 452 | +::: |
| 453 | + |
| 454 | +For spies, fake timers, and more advanced mocking, see |
| 455 | +[Mocking data for tests](/examples/mocking_tutorial/). |
| 456 | + |
420 | 457 | ## Mocking in tests |
421 | 458 |
|
422 | 459 | Mocking is an essential technique for isolating the code being tested from its |
|
0 commit comments