Testes com NestJs e Prisma
Tags: javascript, braziliandevs, testing, nestjs
Published: 2022-05-06 19:59:49

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

```typescript
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);
  }
}
```


## 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.

```typescript
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();
    }
  }
}
```

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:

```typescript
// 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
  },
};
```

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

```typescript
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);
  });
});
```

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.

```typescript
  afterEach(() => {
    jest.clearAllMocks();
  });
```

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

```typescript
  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 */);
    });
  });
```

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:

```typescript
  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 },
      });
    });
  });
```

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:

```typescript
  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 },
      });
    });
  });
```


## 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