Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🧪
Intermediaire 25 min NestJS

Tester NestJS avec Jest

Pourquoi Tester NestJS avec Jest ?

Dans un environnement professionnel, il est essentiel d'avoir une couverture de tests robuste pour assurer la qualité et la fiabilité de nos applications. Les développeurs NestJS ont souvent besoin d'un outil puissant pour tester leurs applications backend. Jest est l'une des solutions les plus populaires pour ce genre de tâche. Il offre un ensemble complet de fonctionnalités pour tester du code simple aux systèmes complexes, tout en facilitant la création et le maintien de tests efficaces.

Un cas d'utilisation concret : imaginez que vous développez une API RESTful pour gérer les utilisateurs de votre application. Vous voudriez vous assurer que chaque endpoint fonctionne correctement, que toutes les validations des données sont effectuées, et que la gestion des erreurs est appropriée.

Prerequis

  • Connaissance approfondie de NestJS
  • Familiarité avec TypeScript
  • Jest installé sur votre machine (version recommandée : 26.0.1)
  • Node.js et npm installés (versions recommandées : Node.js v14.x ou v16.x, npm v7.x)

Concepts fondamentaux

1. Installation de Jest

Avant de commencer à tester votre projet NestJS avec Jest, vous devez l'installer. Ouvrez un terminal et exécutez la commande suivante :

npm install --save-dev jest @nestjs/testing ts-jest

Cela installe Jest, le module @nestjs/testing, et ts-jest pour permettre à Jest de travailler avec TypeScript.

2. Configuration de Jest

Créez un fichier jest.config.js à la racine de votre projet et ajoutez les configurations suivantes :

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  coverageDirectory: 'coverage',
};

3. Création d'un Test pour un Service

Supposons que vous ayez un service UserService dans votre projet NestJS. Voici comment vous pouvez créer un test pour ce service :

// src/user/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  private readonly users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ];

  findAll(): any[] {
    return this.users;
  }

  findOne(id: number): any {
    return this.users.find(user => user.id === id);
  }
}

Pour tester ce service, créez un fichier user.service.spec.ts :

// src/user/user.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UserService],
    }).compile();

    service = module.get<UserService>(UserService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('findAll() should return an array of users', () => {
    const result = service.findAll();
    expect(result).toEqual([
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Doe' },
    ]);
  });

  it('findOne(1) should return the user with id 1', () => {
    const result = service.findOne(1);
    expect(result).toEqual({ id: 1, name: 'John Doe' });
  });
});

4. Exécution des Tests

Pour exécuter vos tests, utilisez la commande suivante :

npx jest

Cela lancera les tests et vous affichera le résultat dans votre terminal.

Mise en pratique : projet fil rouge

Dans cette section, nous allons construire un mini-projet complet et réaliste en utilisant NestJS et Jest. Nous allons créer une API de blog simple qui permet d'ajouter des articles et de les récupérer.

Étape 1 : Création du Projet

Commencez par créer un nouveau projet NestJS :

nest new blog-api
cd blog-api

Étape 2 : Création du Controller

Créez un controller posts.controller.ts pour gérer les endpoints de l'API :

// src/posts/posts.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { PostsService } from './posts.service';

@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Post()
  create(@Body() post: any) {
    return this.postsService.create(post);
  }

  @Get()
  findAll() {
    return this.postsService.findAll();
  }
}

Étape 3 : Création du Service

Créez un service posts.service.ts pour gérer la logique métier :

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostsService {
  private readonly posts = [];

  create(post: any) {
    this.posts.push(post);
    return post;
  }

  findAll(): any[] {
    return this.posts;
  }
}

Étape 4 : Création du Test pour le Controller

Créez un fichier posts.controller.spec.ts pour tester le controller :

// src/posts/posts.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';

describe('PostsController', () => {
  let controller: PostsController;
  let service: PostsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [PostsController],
      providers: [
        PostsService,
        {
          provide: 'POSTS_SERVICE',
          useValue: {},
        },
      ],
    }).compile();

    controller = module.get<PostsController>(PostsController);
    service = module.get<PostsService>('POSTS_SERVICE');
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('create()', () => {
    it('should create a post', () => {
      const post = { title: 'Test Post', content: 'This is a test post' };
      jest.spyOn(service, 'create').mockImplementation(() => post);
      expect(controller.create(post)).toBe(post);
    });
  });

  describe('findAll()', () => {
    it('should return an array of posts', () => {
      const posts = [{ title: 'Post 1', content: 'Content 1' }, { title: 'Post 2', content: 'Content 2' }];
      jest.spyOn(service, 'findAll').mockImplementation(() => posts);
      expect(controller.findAll()).toBe(posts);
    });
  });
});

Étape 5 : Exécution des Tests

Exécutez les tests en utilisant la commande suivante :

npx jest

Erreurs frequentes et debugging

1. TypeError: Cannot read property 'mockImplementation' of undefined

Erreur liée à l'utilisation incorrecte du mock.

// ❌ Mauvais
jest.spyOn(service, 'create').mockImplementation(() => post);

// ✅ Correct
const spy = jest.spyOn(service, 'create');
spy.mockImplementation(() => post);

2. Error: Expected 1 to be undefined

Erreur liée à une assertion incorrecte.

// ❌ Mauvais
expect(controller.create(post)).toBeUndefined();

// ✅ Correct
expect(controller.create(post)).toEqual(post);

3. SyntaxError: Unexpected token import

Erreur liée au format de code TypeScript.

// ❌ Mauvais
import { Module } from '@nestjs/common';

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {}

// ✅ Correct
import { Module } from '@nestjs/common';

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {}

Pour aller plus loin

1. Utilisation de Spies pour les fonctions asynchrones

Vous pouvez utiliser des spies pour tester les fonctions asynchrones.

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostsService {
  private readonly posts = [];

  async create(post: any) {
    this.posts.push(post);
    return post;
  }

  async findAll(): Promise<any[]> {
    return new Promise(resolve => resolve(this.posts));
  }
}
typescript
// src/posts/posts.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';

describe('PostsController', () => {
  let controller: PostsController;
  let service: PostsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [PostsController],
      providers: [
        PostsService,
        {
          provide: 'POSTS_SERVICE',
          useValue: {},
        },
      ],
    }).compile();

    controller = module.get<PostsController>(PostsController);
    service = module.get<PostsService>('POSTS_SERVICE');
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('create()', () => {
    it('should create a post', async () => {
      const post = { title: 'Test Post', content: 'This is a test post' };
      jest.spyOn(service, 'create').mockImplementation(() => Promise.resolve(post));
      expect(await controller.create(post)).toEqual(post);
    });
  });

  describe('findAll()', () => {
    it('should return an array of posts', async () => {
      const posts = [{ title: 'Post 1', content: 'Content 1' }, { title: 'Post 2', content: 'Content 2' }];
      jest.spyOn(service, 'findAll').mockImplementation(() => Promise.resolve(posts));
      expect(await controller.findAll()).toEqual(posts);
    });
  });
});

2. Utilisation de Mocks pour les dépendances externes

Vous pouvez utiliser des mocks pour tester les dépendances externes.

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostsService {
  private readonly posts = [];

  async create(post: any) {
    this.posts.push(post);
    return post;
  }

  async findAll(): Promise<any[]> {
    return new Promise(resolve => resolve(this.posts));
  }
}
typescript
// src/posts/posts.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';

describe('PostsController', () => {
  let controller: PostsController;
  let service: PostsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [PostsController],
      providers: [
        PostsService,
        {
          provide: 'POSTS_SERVICE',
          useValue: {},
        },
      ],
    }).compile();

    controller = module.get<PostsController>(PostsController);
    service = module.get<PostsService>('POSTS_SERVICE');
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('create()', () => {
    it('should create a post', async () => {
      const post = { title: 'Test Post', content: 'This is a test post' };
      jest.spyOn(service, 'create').mockImplementation(() => Promise.resolve(post));
      expect(await controller.create(post)).toEqual(post);
    });
  });

  describe('findAll()', () => {
    it('should return an array of posts', async () => {
      const posts = [{ title: 'Post 1', content: 'Content 1' }, { title: 'Post 2', content: 'Content 2' }];
      jest.spyOn(service, 'findAll').mockImplementation(() => Promise.resolve(posts));
      expect(await controller.findAll()).toEqual(posts);
    });
  });
});

3. Utilisation de Mocks pour les middlewares

Vous pouvez utiliser des mocks pour tester les middlewares.

// src/posts/posts.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class PostsMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Posts Middleware');
    next();
  }
}
typescript
// src/posts/posts.controller.ts
import { Controller, Get, UseMiddleware } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsMiddleware } from './posts.middleware';

@Controller('posts')
@UseMiddleware(PostsMiddleware)
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Get()
  findAll() {
    return this.postsService.findAll();
  }
}
typescript
// src/posts/posts.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
import { PostsMiddleware } from './posts.middleware';

describe('PostsController', () => {
  let controller: PostsController;
  let service: PostsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [PostsController],
      providers: [
        PostsService,
        {
          provide: 'POSTS_SERVICE',
          useValue: {},
        },
      ],
    }).compile();

    controller = module.get<PostsController>(PostsController);
    service = module.get<PostsService>('POSTS_SERVICE');
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('findAll()', () => {
    it('should return an array of posts', async () => {
      const posts = [{ title: 'Post 1', content: 'Content 1' }, { title: 'Post 2', content: 'Content 2' }];
      jest.spyOn(service, 'findAll').mockImplementation(() => Promise.resolve(posts));
      expect(await controller.findAll()).toEqual(posts);
    });
  });
});

Défi pratique

Défi : Créer un test pour une fonction asynchrone dans un service

  1. Créez un service users.service.ts avec une méthode findByName(name: string) qui retourne un utilisateur en fonction de son nom.
  2. Créez un test pour cette méthode dans users.service.spec.ts.
  3. Assurez-vous que le test vérifie correctement les différents cas (utilisateur trouvé, utilisateur non trouvé).
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  private readonly users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ];

  async findByName(name: string): Promise<any> {
    return new Promise(resolve => resolve(this.users.find(user => user.name === name)));
  }
}
typescript
// src/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';

describe('UsersService', () => {
  let service: UsersService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService],
    }).compile();

    service = module.get<UsersService>(UsersService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findByName()', () => {
    it('should return the user with name "John Doe"', async () => {
      const user = await service.findByName('John Doe');
      expect(user).toEqual({ id: 1, name: 'John Doe' });
    });

    it('should return undefined if no user is found', async () => {
      const user = await service.findByName('Jane Smith');
      expect(user).toBeUndefined();
    });
  });
});

En suivant ce défi, vous pouvez vous assurer que vous maîtrisez bien la façon de tester des fonctions asynchrones et des services dans NestJS.

Besoin d'aide sur NestJS ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Comment installer Jest pour tester un projet NestJS ?
Pour installer Jest dans votre projet NestJS, vous pouvez utiliser la commande `npm install --save-dev jest @types/jest ts-jest`. Vous devrez également configurer Jest en ajoutant un fichier `jest.config.js` à la racine de votre projet.
Quelles sont les principales commandes Jest pour tester NestJS ?
Les principales commandes Jest pour tester un projet NestJS incluent : `npm test` pour exécuter tous les tests, `npm run test:cov` pour générer un rapport de couverture des tests, et `npm run test:e2e` pour exécuter les tests d'intégration end-to-end.
Comment écrire un test unitaire simple pour une controller NestJS ?
Pour écrire un test unitaire simple pour une controller NestJS, vous pouvez utiliser la méthode `Test.createTestingModule` pour créer un module de test. Ensuite, injectez le service ou la controller dans le test et utilisez des expectatifs Jest pour vérifier les résultats.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.