Skip to content

Commit 06ee40a

Browse files
fibibotbartlomieju
andauthored
rewrite async test mocking example to use stub() with auto-restore (#3119)
Co-authored-by: fibibot <fibibot@users.noreply.github.com> Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
1 parent f3b54f0 commit 06ee40a

1 file changed

Lines changed: 53 additions & 16 deletions

File tree

examples/tutorials/testing.md

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
last_modified: 2025-09-10
2+
last_modified: 2026-05-14
33
title: "Writing tests"
44
description: "Learn key concepts like test setup and structure, assertions, async testing, mocking, test fixtures, and code coverage"
55
url: /examples/testing_tutorial/
@@ -375,48 +375,85 @@ Deno.test("async test example", async () => {
375375

376376
### Testing async functions
377377

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.
379385

380-
```ts
381-
// async-function.ts
386+
```ts title="async_function.ts"
382387
export async function fetchUserData(userId: string) {
383388
const response = await fetch(`https://api.example.com/users/${userId}`);
384389
if (!response.ok) {
385390
throw new Error(`Failed to fetch user: ${response.status}`);
386391
}
387392
return await response.json();
388393
}
394+
```
389395

390-
// async-function_test.ts
396+
```ts title="async_function_test.ts"
391397
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";
393400

394401
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+
);
400413

401414
const userData = await fetchUserData("123");
402415
assertEquals(userData.id, "123");
403416
assertEquals(userData.name, "Test User");
404417
});
405418

406419
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+
);
411425

412426
await assertRejects(
413-
async () => await fetchUserData("nonexistent"),
427+
() => fetchUserData("nonexistent"),
414428
Error,
415429
"Failed to fetch user: 404",
416430
);
417431
});
418432
```
419433

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+
420457
## Mocking in tests
421458

422459
Mocking is an essential technique for isolating the code being tested from its

0 commit comments

Comments
 (0)