[{"data":1,"prerenderedAt":565},["ShallowReactive",2],{"article-\u002Fblog\u002Ftester-une-api-nestjs-jest-supertest":3},{"id":4,"title":5,"body":6,"brouillon":551,"date":552,"description":553,"extension":554,"image":555,"imageAlt":555,"meta":556,"navigation":123,"path":557,"seo":558,"stem":559,"tags":560,"__hash__":564},"blog\u002Fblog\u002Ftester-une-api-nestjs-jest-supertest.md","Tester une API NestJS : Jest, mocks et tests e2e avec Supertest",{"type":7,"value":8,"toc":545},"minimark",[9,13,18,26,356,372,375,395,399,402,501,504,508,534,538,541],[10,11,12],"p",{},"Le système d'injection de dépendances de NestJS n'est pas qu'une élégance\nd'architecture : c'est ce qui rend le backend profondément testable. Chaque\ndépendance déclarée dans un constructeur est un point de substitution pour\nles tests. Voici comment j'organise la stratégie, du test unitaire au test\nend-to-end.",[14,15,17],"h2",{"id":16},"tests-unitaires-le-module-de-test-et-les-mocks","Tests unitaires : le module de test et les mocks",[10,19,20,21,25],{},"NestJS fournit ",[22,23,24],"code",{},"Test.createTestingModule",", qui reconstruit un mini-conteneur\nd'injection où chaque provider peut être remplacé :",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-typescript shiki shiki-themes github-dark","describe('DocumentsService', () => {\n  let service: DocumentsService;\n  const prisma = {\n    document: { findMany: jest.fn(), create: jest.fn() },\n  };\n\n  beforeEach(async () => {\n    const module = await Test.createTestingModule({\n      providers: [\n        DocumentsService,\n        { provide: PrismaService, useValue: prisma },\n      ],\n    }).compile();\n\n    service = module.get(DocumentsService);\n    jest.clearAllMocks();\n  });\n\n  it('refuse l\\'accès à un document d\\'un autre patient', async () => {\n    prisma.document.findMany.mockResolvedValue([]);\n\n    await expect(\n      service.getForPatient('doc-1', 'patient-B'),\n    ).rejects.toThrow(ForbiddenException);\n  });\n});\n","typescript","",[22,34,35,62,80,95,112,118,125,143,166,172,178,184,190,202,207,227,238,244,249,282,294,299,311,333,345,350],{"__ignoreMap":32},[36,37,40,44,48,52,55,59],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"svObZ","describe",[36,45,47],{"class":46},"s95oV","(",[36,49,51],{"class":50},"sU2Wk","'DocumentsService'",[36,53,54],{"class":46},", () ",[36,56,58],{"class":57},"snl16","=>",[36,60,61],{"class":46}," {\n",[36,63,65,68,71,74,77],{"class":38,"line":64},2,[36,66,67],{"class":57},"  let",[36,69,70],{"class":46}," service",[36,72,73],{"class":57},":",[36,75,76],{"class":42}," DocumentsService",[36,78,79],{"class":46},";\n",[36,81,83,86,90,93],{"class":38,"line":82},3,[36,84,85],{"class":57},"  const",[36,87,89],{"class":88},"sDLfK"," prisma",[36,91,92],{"class":57}," =",[36,94,61],{"class":46},[36,96,98,101,104,107,109],{"class":38,"line":97},4,[36,99,100],{"class":46},"    document: { findMany: jest.",[36,102,103],{"class":42},"fn",[36,105,106],{"class":46},"(), create: jest.",[36,108,103],{"class":42},[36,110,111],{"class":46},"() },\n",[36,113,115],{"class":38,"line":114},5,[36,116,117],{"class":46},"  };\n",[36,119,121],{"class":38,"line":120},6,[36,122,124],{"emptyLinePlaceholder":123},true,"\n",[36,126,128,131,133,136,139,141],{"class":38,"line":127},7,[36,129,130],{"class":42},"  beforeEach",[36,132,47],{"class":46},[36,134,135],{"class":57},"async",[36,137,138],{"class":46}," () ",[36,140,58],{"class":57},[36,142,61],{"class":46},[36,144,146,149,152,154,157,160,163],{"class":38,"line":145},8,[36,147,148],{"class":57},"    const",[36,150,151],{"class":88}," module",[36,153,92],{"class":57},[36,155,156],{"class":57}," await",[36,158,159],{"class":46}," Test.",[36,161,162],{"class":42},"createTestingModule",[36,164,165],{"class":46},"({\n",[36,167,169],{"class":38,"line":168},9,[36,170,171],{"class":46},"      providers: [\n",[36,173,175],{"class":38,"line":174},10,[36,176,177],{"class":46},"        DocumentsService,\n",[36,179,181],{"class":38,"line":180},11,[36,182,183],{"class":46},"        { provide: PrismaService, useValue: prisma },\n",[36,185,187],{"class":38,"line":186},12,[36,188,189],{"class":46},"      ],\n",[36,191,193,196,199],{"class":38,"line":192},13,[36,194,195],{"class":46},"    }).",[36,197,198],{"class":42},"compile",[36,200,201],{"class":46},"();\n",[36,203,205],{"class":38,"line":204},14,[36,206,124],{"emptyLinePlaceholder":123},[36,208,210,213,216,218,221,224],{"class":38,"line":209},15,[36,211,212],{"class":46},"    service ",[36,214,215],{"class":57},"=",[36,217,151],{"class":88},[36,219,220],{"class":46},".",[36,222,223],{"class":42},"get",[36,225,226],{"class":46},"(DocumentsService);\n",[36,228,230,233,236],{"class":38,"line":229},16,[36,231,232],{"class":46},"    jest.",[36,234,235],{"class":42},"clearAllMocks",[36,237,201],{"class":46},[36,239,241],{"class":38,"line":240},17,[36,242,243],{"class":46},"  });\n",[36,245,247],{"class":38,"line":246},18,[36,248,124],{"emptyLinePlaceholder":123},[36,250,252,255,257,260,263,266,268,271,274,276,278,280],{"class":38,"line":251},19,[36,253,254],{"class":42},"  it",[36,256,47],{"class":46},[36,258,259],{"class":50},"'refuse l",[36,261,262],{"class":88},"\\'",[36,264,265],{"class":50},"accès à un document d",[36,267,262],{"class":88},[36,269,270],{"class":50},"un autre patient'",[36,272,273],{"class":46},", ",[36,275,135],{"class":57},[36,277,138],{"class":46},[36,279,58],{"class":57},[36,281,61],{"class":46},[36,283,285,288,291],{"class":38,"line":284},20,[36,286,287],{"class":46},"    prisma.document.findMany.",[36,289,290],{"class":42},"mockResolvedValue",[36,292,293],{"class":46},"([]);\n",[36,295,297],{"class":38,"line":296},21,[36,298,124],{"emptyLinePlaceholder":123},[36,300,302,305,308],{"class":38,"line":301},22,[36,303,304],{"class":57},"    await",[36,306,307],{"class":42}," expect",[36,309,310],{"class":46},"(\n",[36,312,314,317,320,322,325,327,330],{"class":38,"line":313},23,[36,315,316],{"class":46},"      service.",[36,318,319],{"class":42},"getForPatient",[36,321,47],{"class":46},[36,323,324],{"class":50},"'doc-1'",[36,326,273],{"class":46},[36,328,329],{"class":50},"'patient-B'",[36,331,332],{"class":46},"),\n",[36,334,336,339,342],{"class":38,"line":335},24,[36,337,338],{"class":46},"    ).rejects.",[36,340,341],{"class":42},"toThrow",[36,343,344],{"class":46},"(ForbiddenException);\n",[36,346,348],{"class":38,"line":347},25,[36,349,243],{"class":46},[36,351,353],{"class":38,"line":352},26,[36,354,355],{"class":46},"});\n",[10,357,358,359,363,364,367,368,371],{},"Le pattern clé : ",[360,361,362],"strong",{},"mocker à la frontière"," (Prisma, services externes,\nfiles d'attente), tester la logique réelle du service. Pas besoin de\nmockito ici : les ",[22,365,366],{},"jest.fn()"," natifs suffisent, et ",[22,369,370],{},"useValue"," les injecte à\nla place des vraies dépendances.",[10,373,374],{},"Deux conseils issus de l'expérience :",[376,377,378,385],"ul",{},[379,380,381,384],"li",{},[360,382,383],{},"Testez les cas d'interdiction autant que les cas de succès."," Sur une\nAPI qui manipule des données sensibles, les tests les plus précieux sont\nceux qui vérifient qu'un utilisateur ne peut PAS accéder à ce qui ne lui\nappartient pas : guards, ownership, scopes.",[379,386,387,390,391,394],{},[360,388,389],{},"Ne testez pas le framework."," Vérifier qu'un décorateur ",[22,392,393],{},"@Get()"," route\nbien une requête, c'est tester NestJS, pas votre code. Votre logique de\nservice et vos règles d'accès, elles, méritent chaque test.",[14,396,398],{"id":397},"tests-e2e-supertest-contre-lapplication-réelle","Tests e2e : Supertest contre l'application réelle",[10,400,401],{},"Le test end-to-end monte l'application complète (guards, pipes,\ninterceptors, validation) et l'attaque en HTTP via Supertest :",[27,403,405],{"className":29,"code":404,"language":31,"meta":32,"style":32},"it('POST \u002Fauth\u002Fverify-otp → 401 sur code invalide', async () => {\n  await request(app.getHttpServer())\n    .post('\u002Fauth\u002Fverify-otp')\n    .send({ phone: '+242066000000', code: '000000' })\n    .expect(401);\n});\n",[22,406,407,427,444,460,482,497],{"__ignoreMap":32},[36,408,409,412,414,417,419,421,423,425],{"class":38,"line":39},[36,410,411],{"class":42},"it",[36,413,47],{"class":46},[36,415,416],{"class":50},"'POST \u002Fauth\u002Fverify-otp → 401 sur code invalide'",[36,418,273],{"class":46},[36,420,135],{"class":57},[36,422,138],{"class":46},[36,424,58],{"class":57},[36,426,61],{"class":46},[36,428,429,432,435,438,441],{"class":38,"line":64},[36,430,431],{"class":57},"  await",[36,433,434],{"class":42}," request",[36,436,437],{"class":46},"(app.",[36,439,440],{"class":42},"getHttpServer",[36,442,443],{"class":46},"())\n",[36,445,446,449,452,454,457],{"class":38,"line":82},[36,447,448],{"class":46},"    .",[36,450,451],{"class":42},"post",[36,453,47],{"class":46},[36,455,456],{"class":50},"'\u002Fauth\u002Fverify-otp'",[36,458,459],{"class":46},")\n",[36,461,462,464,467,470,473,476,479],{"class":38,"line":97},[36,463,448],{"class":46},[36,465,466],{"class":42},"send",[36,468,469],{"class":46},"({ phone: ",[36,471,472],{"class":50},"'+242066000000'",[36,474,475],{"class":46},", code: ",[36,477,478],{"class":50},"'000000'",[36,480,481],{"class":46}," })\n",[36,483,484,486,489,491,494],{"class":38,"line":114},[36,485,448],{"class":46},[36,487,488],{"class":42},"expect",[36,490,47],{"class":46},[36,492,493],{"class":88},"401",[36,495,496],{"class":46},");\n",[36,498,499],{"class":38,"line":120},[36,500,355],{"class":46},[10,502,503],{},"C'est ici qu'on attrape ce que les tests unitaires ne voient pas : un pipe\nde validation mal configuré, un guard oublié sur une route, un DTO qui\nlaisse passer un champ inattendu. Pour la base de données, deux écoles :\nune base de test dédiée (fidélité maximale, mes préférences pour les flux\ncritiques) ou le mock du service de données (rapidité). L'important est de\nchoisir consciemment par type de test, pas par accident.",[14,505,507],{"id":506},"lorganisation-qui-tient-dans-le-temps","L'organisation qui tient dans le temps",[376,509,510,520,531],{},[379,511,512,515,516,519],{},[22,513,514],{},"*.spec.ts"," à côté du code pour l'unitaire, ",[22,517,518],{},"test\u002F*.e2e-spec.ts"," pour le\ne2e : la convention NestJS, autant la suivre.",[379,521,522,523,526,527,530],{},"Un ",[22,524,525],{},"beforeEach"," qui nettoie les mocks (",[22,528,529],{},"jest.clearAllMocks",") : les tests\nqui dépendent de l'ordre d'exécution sont une dette qui explose toujours.",[379,532,533],{},"Les tests e2e couvrent les parcours (inscription → OTP → accès protégé),\npas chaque endpoint isolément.",[14,535,537],{"id":536},"la-leçon","La leçon",[10,539,540],{},"La testabilité d'un backend NestJS se décide dans l'architecture : si tout\npasse par l'injection, tout se substitue, donc tout se teste. Le jour où un\ntest est difficile à écrire, ce n'est presque jamais un problème de test,\nc'est le code qui révèle un couplage à corriger.",[542,543,544],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":32,"searchDepth":64,"depth":64,"links":546},[547,548,549,550],{"id":16,"depth":64,"text":17},{"id":397,"depth":64,"text":398},{"id":506,"depth":64,"text":507},{"id":536,"depth":64,"text":537},false,"2026-07-07","Tests unitaires de services, mock des dépendances via le système d'injection, et tests end-to-end : la stratégie de test d'un backend NestJS.","md",null,{},"\u002Fblog\u002Ftester-une-api-nestjs-jest-supertest",{"title":5,"description":553},"blog\u002Ftester-une-api-nestjs-jest-supertest",[561,562,563],"NestJS","Tests","Backend","BKlPAf-y5M4ogYdKhRadlK9s2mwk7uVA_0vQVi9ADjA",1784652016702]