Skip to content

Commit 66beb9f

Browse files
fibibotbartlomieju
andauthored
docs: import AuthService in mocking tutorial (#3135)
Co-authored-by: fibibot <fibibot@users.noreply.github.com> Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
1 parent 8dd3959 commit 66beb9f

1 file changed

Lines changed: 58 additions & 64 deletions

File tree

examples/tutorials/mocking.md

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -548,25 +548,20 @@ authentication service that:
548548
2. Calls an API to authenticate
549549
3. Stores tokens with expiration times
550550

551-
In the example below, we'll create a full `AuthService` class that handles user
552-
login, token management, and authentication. We'll test it thoroughly using
553-
various mocking techniques covered earlier: stubbing fetch requests, spying on
554-
methods, and manipulating time to test token expiration - all within organized
555-
test steps.
551+
In the example below, we'll create a full `AuthService` class in an application
552+
module that handles user login, token management, and authentication. Then we'll
553+
import it from a test module and test it thoroughly using various mocking
554+
techniques covered earlier: stubbing fetch requests, spying on methods, and
555+
manipulating time to test token expiration, all within organized test steps.
556556

557557
Deno's testing API provides a useful `t.step()` function that allows you to
558558
organize your tests into logical steps or sub-tests. This makes complex tests
559559
more readable and helps pinpoint exactly which part of a test is failing. Each
560560
step can have its own assertions and will be reported separately in the test
561-
output.
561+
output. First, define the service in its own module:
562562

563-
```ts
564-
import { assertEquals, assertRejects } from "jsr:@std/assert";
565-
import { spy, stub } from "jsr:@std/testing/mock";
566-
import { FakeTime } from "jsr:@std/testing/time";
567-
568-
// The service we want to test
569-
class AuthService {
563+
```ts title="auth_service.ts"
564+
export class AuthService {
570565
private token: string | null = null;
571566
private expiresAt: Date | null = null;
572567

@@ -587,13 +582,13 @@ class AuthService {
587582
throw new Error(`Authentication failed: ${response.status}`);
588583
}
589584

590-
const data = await response.json();
585+
const data: { token: string } = await response.json();
591586

592587
// Store token with expiration (1 hour)
593588
this.token = data.token;
594589
this.expiresAt = new Date(Date.now() + 60 * 60 * 1000);
595590

596-
return this.token;
591+
return data.token;
597592
}
598593

599594
getToken(): string {
@@ -615,6 +610,15 @@ class AuthService {
615610
this.expiresAt = null;
616611
}
617612
}
613+
```
614+
615+
Then import the service from your test file:
616+
617+
```ts title="auth_service_test.ts"
618+
import { assertEquals, assertRejects, assertThrows } from "jsr:@std/assert";
619+
import { assertSpyCalls, spy, stub } from "jsr:@std/testing/mock";
620+
import { FakeTime } from "jsr:@std/testing/time";
621+
import { AuthService } from "./auth_service.ts";
618622

619623
Deno.test("AuthService comprehensive test", async (t) => {
620624
await t.step("login should validate credentials", async () => {
@@ -635,7 +639,7 @@ Deno.test("AuthService comprehensive test", async (t) => {
635639
{ status: 200, headers: { "Content-Type": "application/json" } },
636640
);
637641

638-
const fetchStub = stub(
642+
using fetchStub = stub(
639643
globalThis,
640644
"fetch",
641645
(_url: string | URL | Request, options?: RequestInit) => {
@@ -649,69 +653,59 @@ Deno.test("AuthService comprehensive test", async (t) => {
649653
},
650654
);
651655

652-
try {
653-
const token = await authService.login("testuser", "password123");
654-
assertEquals(token, "fake-jwt-token");
655-
} finally {
656-
fetchStub.restore();
657-
}
656+
const token = await authService.login("testuser", "password123");
657+
assertEquals(token, "fake-jwt-token");
658+
assertSpyCalls(fetchStub, 1);
658659
});
659660

660-
await t.step("token expiration should work correctly", () => {
661-
using fakeTime = new FakeTime();
662-
661+
await t.step("token expiration should work correctly", async () => {
662+
using time = new FakeTime(new Date("2023-01-01T12:00:00Z"));
663663
const authService = new AuthService();
664-
const time = fakeTime(new Date("2023-01-01T12:00:00Z"));
665664

666-
try {
667-
// Mock the login process to set token directly
668-
authService.login = spy(
669-
authService,
670-
"login",
671-
async () => {
672-
(authService as any).token = "fake-token";
673-
(authService as any).expiresAt = new Date(
674-
Date.now() + 60 * 60 * 1000,
675-
);
676-
return "fake-token";
677-
},
678-
);
679-
680-
// Login and verify token
681-
authService.login("user", "pass").then(() => {
682-
const token = authService.getToken();
683-
assertEquals(token, "fake-token");
684-
685-
// Advance time past expiration
686-
time.tick(61 * 60 * 1000);
687-
688-
// Token should now be expired
689-
assertRejects(
690-
() => {
691-
authService.getToken();
692-
},
693-
Error,
694-
"Token expired",
695-
);
696-
});
697-
} finally {
698-
time.restore();
699-
(authService.login as any).restore();
700-
}
665+
using fetchStub = stub(
666+
globalThis,
667+
"fetch",
668+
() =>
669+
Promise.resolve(
670+
new Response(JSON.stringify({ token: "fake-token" }), {
671+
status: 200,
672+
headers: { "Content-Type": "application/json" },
673+
}),
674+
),
675+
);
676+
677+
using getTokenSpy = spy(authService, "getToken");
678+
679+
await authService.login("user", "pass");
680+
assertEquals(authService.getToken(), "fake-token");
681+
assertSpyCalls(getTokenSpy, 1);
682+
683+
// Advance time past expiration
684+
time.tick(61 * 60 * 1000);
685+
686+
// Token should now be expired
687+
assertThrows(
688+
() => authService.getToken(),
689+
Error,
690+
"Token expired",
691+
);
692+
assertSpyCalls(getTokenSpy, 2);
693+
assertSpyCalls(fetchStub, 1);
701694
});
702695
});
703696
```
704697

705-
This code defines `AuthService` class with three main functionalities:
698+
The `auth_service.ts` module defines an `AuthService` class with three main
699+
functionalities:
706700

707701
- Login - Validates credentials, calls an API, and stores a token with an
708702
expiration time
709703
- GetToken - Returns the token if valid and not expired
710704
- Logout - Clears the token and expiration
711705

712706
The testing structure is organized as a single main test with three logical
713-
**steps**, each testing a different aspect of the service; credential
714-
validation, API call handling and token expiration.
707+
**steps**, each testing a different aspect of the service: credential
708+
validation, API call handling, and token expiration.
715709

716710
🦕 Effective mocking is essential for writing reliable, maintainable unit tests.
717711
Deno provides several powerful tools to help you isolate your code during

0 commit comments

Comments
 (0)