@@ -548,25 +548,20 @@ authentication service that:
5485482 . Calls an API to authenticate
5495493 . 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
557557Deno's testing API provides a useful ` t.step() ` function that allows you to
558558organize your tests into logical steps or sub-tests. This makes complex tests
559559more readable and helps pinpoint exactly which part of a test is failing. Each
560560step 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
619623Deno .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
712706The 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.
717711Deno provides several powerful tools to help you isolate your code during
0 commit comments