DEV Community

Cover image for Testes com NestJs e Prisma
Vitor Martins
Vitor Martins

Posted on

Testes com NestJs e Prisma

O NestJs é uma ferramenta incrível que descobri não faz muito tempo. Como não sou muito familiarizado com o backend, tive um pouco de dificuldade para desenvolver alguns testes com o NestJs e também o Prisma. Por isso, quero compartilhar esse pequeno passo a passo para que você consiga testar suas aplicações mais facilmente.

Pra esse tutorial pularei a parte de instalação e configuração do Nest e do Prisma. Vamos considerar que temos uma API que precisa acessar um banco de dados SQL e entregar postagens de um blog para o client.


Criando o modulo posts

Vamos começar primeiramente criando o nosso modulo chamado posts, rodando o seguinte comando:

nest generate resource posts

Com esse comando o Nest criará automaticamente um módulo CRUD pronto para ser usado. Vamos então criar nossa entidade chamada Post onde será mapeado a tabela da base de dados

import { Prisma } from '@prisma/client';

export class Post implements Prisma.PostUncheckedCreateInput {
  id?: number;
  titulo: string;
  conteudo?: string;
  publicado?: boolean;
  autor: string;

  constructor(partial: Partial<Post>) {
    Object.assign(this, partial);
  }
}
Enter fullscreen mode Exit fullscreen mode

Criando os serviços

Agora, vamos criar a nossa camada de regras de negócio e interação com a base de dados. No nosso arquivo posts.service.ts teremos um CRUD básico, com métodos para retornar criar, listar, atualizar e deletar posts do nosso blog.

import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';

@Injectable()
export class PostsService {
  constructor(private readonly prisma: PrismaService) {}

  create(createPostDto: CreatePostDto) {
    return this.prisma.post.create({ data: createPostDto });
  }

  findAll() {
    return this.prisma.post.findMany();
  }

  findOne(id: number) {
    return this.prisma.post.findUnique({ where: { id } });
  }

  async update(id: number, updatePostDto: UpdatePostDto) {
    try {
      return await this.prisma.post.update({
        where: { id },
        data: updatePostDto,
      });
    } catch (error) {
      throw new NotFoundException();
    }
  }

  async remove(id: number) {
    try {
      await this.prisma.post.delete({ where: { id } });
    } catch (error) {
      throw new NotFoundException();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Repare que para interagir com a base de dados precisamos declarar o serviço do Prisma no construtor da classe.

Criando nossos testes com o Prisma

Agora, finalmente vamos começar os nossos testes. Começando pela camada de serviços, que onde mais devemos ter testes.
Primeiramente precisamos mockar os retornos dos métodos do Prisma. Para isso é necessário chamar a função jest.fn().mockReturnValue(). Podemos fazer da seguinte maneira:

// Criamos primeiro nos dados fícticios para serem retornados do Prisma
const fakePosts = [
  {
    id: 1,
    titulo: 'Primeiro post do blog',
    conteudo: 'Apenas um teste.',
    publicado: true,
    autor: 'João',
  },
  {
    id: 2,
    titulo: 'Testes unitários',
    conteudo: 'Conteúdo sobre testes unitários.',
    publicado: true,
    autor: 'Vitor',
  },
  {
    id: 3,
    titulo: 'Javascript',
    conteudo: 'Conteúdo sobre testes Javascript.',
    publicado: false,
    autor: 'Teste',
  },
];

// E depois nosso objeto de mock do Prisma, retornando os dados falsos
const prismaMock = {
  post: {
    create: jest.fn().mockReturnValue(fakePosts[0]),
    findMany: jest.fn().mockResolvedValue(fakePosts),
    findUnique: jest.fn().mockResolvedValue(fakePosts[0]),
    update: jest.fn().mockResolvedValue(fakePosts[0]),
    delete: jest.fn(), // O método delete não retorna nada
  },
};
Enter fullscreen mode Exit fullscreen mode

Depois de criar os mocks, vamos configurar o testing module do Nest.

describe('PostsService', () => {
  let service: PostsService;
  let prisma: PrismaService;

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

    service = module.get<PostsService>(PostsService);
    prisma = module.get<PrismaService>(PrismaService);
  });
});
Enter fullscreen mode Exit fullscreen mode

Primeiro declaramos a variável para o service do Prisma. Depois adicionamos um provider mockado do PrismaService e usavamos como valor o mock que criamos anteriormente. E por último inicializamos a variável do serviço e do Prisma.

Logo após a declaração do beforeEach vamos declarar o afterEach para limparmos os mocks apos a execução de cada teste.

  afterEach(() => {
    jest.clearAllMocks();
  });
Enter fullscreen mode Exit fullscreen mode

Agora vamos para o nosso primeiro cenário de testes que vai ser o em cima do método findAll

  describe('findAll', () => {
    it(`should return an array of posts`, async () => {
      const response = await service.findAll();

      expect(response).toEqual(fakePosts);
      expect(prisma.post.findMany).toHaveBeenCalledTimes(1);
      expect(prisma.post.findMany).toHaveBeenCalledWith(/* nothing */);
    });
  });
Enter fullscreen mode Exit fullscreen mode

Não temos nada de difícil aqui. Primeiro nós precisamos chamar nosso método do service e depois criar nossos expects para validar o que for necessário. Podemos validar um método do Prisma através do objeto que criamos no começo para ele (prisma.post.findMany) para esse caso.

Para o cenário de findOne teremos:

  describe('findOne', () => {
    it(`should return a single post`, async () => {
      const response = await service.findOne(1);

      expect(response).toEqual(fakePosts[0]);
      expect(prisma.post.findUnique).toHaveBeenCalledTimes(1);
      expect(prisma.post.findUnique).toHaveBeenCalledWith({
        where: { id: 1 },
      });
    });

    it(`should return nothing when post is not found`, async () => {
      jest.spyOn(prisma.post, 'findUnique').mockResolvedValue(undefined);

      const response = await service.findOne(99);

      expect(response).toBeUndefined();
      expect(prisma.post.findUnique).toHaveBeenCalledTimes(1);
      expect(prisma.post.findUnique).toHaveBeenCalledWith({
        where: { id: 99 },
      });
    });
  });
Enter fullscreen mode Exit fullscreen mode

Repare que para o segundo it usei a função spyOn do Jest. Precisamos usar ela para sobrepor o mock que criamos no começo, pois para esse caso estamos testando se o serviço retorna undefined quando não é encontrado nenhuma postagem com um determinado id. Então, com o spyOn podemos mockar que o retorno do método findUnique será undefined.

Para o resto dos métodos teremos os mesmos conceitos:

  describe('create', () => {
    it(`should create a new post`, async () => {
      const response = await service.create(fakePosts[0]);

      expect(response).toBe(fakePosts[0]);
      expect(prisma.post.create).toHaveBeenCalledTimes(1);
      expect(prisma.post.create).toHaveBeenCalledWith({
        data: fakePosts[0],
      });
    });
  });

  describe('updateOne', () => {
    it(`should update a post`, async () => {
      const response = await service.update(1, fakePosts[0]);

      expect(response).toEqual(fakePosts[0]);
      expect(prisma.post.update).toHaveBeenCalledTimes(1);
      expect(prisma.post.update).toHaveBeenCalledWith({
        where: { id: 1 },
        data: fakePosts[0],
      });
    });

    it(`should return NotFoundException when no post is found`, async () => {
      const unexistingPost = {
        id: 42,
        titulo: 'teste',
        conteudo: 'Conteudo Teste',
        publicado: false,
        autor: 'Teste',
      };

      jest.spyOn(prisma.post, 'update').mockRejectedValue(new Error());

      try {
        await service.update(42, unexistingPost);
      } catch (error) {
        expect(error).toEqual(new NotFoundException());
      }

      expect(prisma.post.update).toHaveBeenCalledWith({
        where: { id: 42 },
        data: unexistingPost,
      });
    });
  });

  describe('deleteOne', () => {
    it(`should delete post and return empty body`, async () => {
      expect(await service.remove(1)).toBeUndefined();
      expect(prisma.post.delete).toHaveBeenCalledTimes(1);
      expect(prisma.post.delete).toHaveBeenCalledWith({ where: { id: 1 } });
    });

    it(`should return NotFoundException if post does not exist`, async () => {
      jest.spyOn(prisma.post, 'delete').mockRejectedValue(new Error());

      try {
        await service.remove(99);
      } catch (error) {
        expect(error).toEqual(new NotFoundException());
      }

      expect(prisma.post.delete).toHaveBeenCalledTimes(1);
      expect(prisma.post.delete).toHaveBeenCalledWith({
        where: { id: 99 },
      });
    });
  });
Enter fullscreen mode Exit fullscreen mode

Conclusão

Espero que tenham gostado desse pequeno tutorial sobre testes com Nest e Prisma, não cobre tudo mas é um bom ponto de partida para criar os primeiros testes com essas ferramentas.
Não passei muito pelo controller e outras seções do modulo do Nest, mas podemos utilizar o controller que o Nest gera automaticamente com a CLI. Você pode conferir no meu repositório do GitHub como fazer os testes para o controller (é bem simples).

Link do repositório: https://github.com/mrtins/nest-blog-posts

Latest comments (5)

Collapse
 
thgoas profile image
Thiago Andrade

pra min deu esse error: Matcher error: received value must be a mock or spy function

Collapse
 
suzanamiceli profile image
suzanaMiceli

Cara, muito obrigada!!!! Vc e ajudou infinitamente!!!!

Collapse
 
elmanoneto profile image
Elmano Neto • Edited

Muito top, @mrtinsvitor!

Comecei a estudar Nest com Prisma e o material foi bem util.

Valeu!

Collapse
 
hamzalak profile image
LAKHAL Hamza

It sames that the article is interessant, can you provide an English version :D

Collapse
 
mrtinsvitor profile image
Vitor Martins

of course, I can work on that as soon as I can