AluraChallenges #2 ( Semana 2)
Gabriel de Luca
Posted on September 1, 2021
No post anterior, nós fizemos todas as implementações que tínhamos da semana 1, deixando nossa API de vídeos pronta para consumo.
Só que ainda temos desafios pela frente e nessa semana faremos a inserção de categorias, para classificarmos os nossos vídeos.
A história dessa semana é a seguinte:
Depois de alguns testes com usuários, foi definido que a próxima feature a ser desenvolvida nesse projeto é a divisão dos vídeos por categoria, para melhorar a experiência de organização da lista de vídeos pelo usuário.
Então, faremos nesse post as seguintes implementações :
-
Adicionar
categorias
e seus campos na base de dados; -
Rotas CRUD para
/categorias
; -
Incluir campo
categoriaId
no modelovideo
; - Escrever os testes unitários.
Vamos começar fazendo os dois primeiros pontos, e novamente usaremos o generate do Nest, para criamos o recurso Categorias.
nest generate resource categorias
Após a criação, faremos a definição da nossa classe CreateCategoriaDto:
// src/categorias/dto/create-categoria.dto.ts
import { Video } from '../../videos/entities/video.entity';
export class CreateCategoriaDto {
id: number;
titulo: string;
cor: string;
videos: Video[];
}
e alteraremos o nosso CreateVideoDto, adicionando a categoria:
// src/videos/dto/create-video.dto.ts
import { Categoria } from '../../categorias/entities/categoria.entity';
export class CreateVideoDto {
...
categoria: Categoria;
}
Agora vamos na nossa "entity" e precisaremos fazer um pouco diferente.
Como será necessário ligar as tabelas de videos e categorias, utilizaremos o decorator @OneToMany(), que significa que teremos 1 categoria para vários vídeos:
// src/categorias/entities/categoria.entity.ts
import { IsNotEmpty, IsString } from 'class-validator';
import { Video } from '../../videos/entities/video.entity';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Categoria {
@PrimaryGeneratedColumn()
id: number;
@IsNotEmpty()
@IsString()
@Column()
titulo: string;
@IsNotEmpty()
@IsString()
@Column()
cor: string;
@OneToMany(() => Video, (video) => video.categoria)
videos: Video[];
}
Será necessário alterar nossa "entity" Video também, informando que teremos varios vídeos para cada categoria. Utilizaremos o decorator @ManyToOne() após todos os atributos que já temos:
// src/videos/entities/video.entity.ts
import { PrimaryGeneratedColumn, Column, Entity, ManyToOne } from 'typeorm';
import { IsNotEmpty, IsString, IsUrl } from 'class-validator';
import { Categoria } from '../../categorias/entities/categoria.entity';
@Entity()
export class Video {
...
@ManyToOne(() => Categoria, (categoria) => categoria.id, { nullable: false })
categoria: Categoria;
}
Entidades ajustadas, vamos para o nosso "categoria.service" para implementar as funcionalidades dela.
A diferença nela está no findAll, que passamos o parâmetro indicando a relação (relations: ['videos']) e já pedindo para trazer os dados dessa relação (loadEagerRelations: true):
// src/categorias/categorias.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateCategoriaDto } from './dto/create-categoria.dto';
import { UpdateCategoriaDto } from './dto/update-categoria.dto';
import { Categoria } from './entities/categoria.entity';
@Injectable()
export class CategoriasService {
@InjectRepository(Categoria)
private categoriaRepository: Repository<Categoria>;
create(createCategoriaDto: CreateCategoriaDto) {
return this.categoriaRepository.save(createCategoriaDto);
}
findAll() {
return this.categoriaRepository.find({
relations: ['videos'],
loadEagerRelations: true,
});
}
findOne(id: number) {
return this.categoriaRepository.findOne(id);
}
update(id: number, updateCategoriaDto: UpdateCategoriaDto) {
return this.categoriaRepository.update(id, updateCategoriaDto);
}
async remove(id: number) {
const categoria = await this.findOne(id);
return this.categoriaRepository.remove(categoria);
}
}
Novamente, temos que ajustar nosso "video.service" também, que no caso, só terá alteração no método "findAll()":
// src/videos/videos.service.ts
...
findAll() {
return this.videoRepository.find({
relations: ['categoria'],
loadEagerRelations: true,
});
}
...
Agora, para que a tabela de categorias seja criada no banco e identificada pelo Nest, precisamos ir no categorias.module e importar o TypeOrmModule passando a Categoria como parâmetro:
// src/categorias/categorias.module.ts
import { Module } from '@nestjs/common';
import { CategoriasService } from './categorias.service';
import { CategoriasController } from './categorias.controller';
import { Categoria } from './entities/categoria.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Categoria])],
controllers: [CategoriasController],
providers: [CategoriasService],
})
export class CategoriasModule {}
Maravilha, implementamos a categoria para os nossos videos, porém, temos um card que pede para que ao acessar a rota "/categorias/:id/videos" retorne os vídeos de uma determinada categoria:
Para que isso seja possível, vamos precisar de uma nova rota no nosso controller de categorias.
A nova rota vai ficar assim:
// src/categorias/categorias.controller.ts
...
@Get(':id/videos')
findVideosByCategoryId(@Param('id') id: string) {
return this.categoriasService.findVideoByCategory(+id);
}
...
Mas espera aí, nós não temos esse método "categoriasService.findVideoByCategory()".
Precisamos criar esse método lá no nosso serviço "categorias.service":
// src/categorias/categorias.service.ts
...
async findVideoByCategory(id: number): Promise<Video[]> {
const categoria = await this.findOne(id);
return categoria.videos;
}
...
Mas se tentarmos acessar a rota, veremos uma tela branca, sem retorno de nenhum dado. --'
Para que seja retornado, precisamos alterar também o nosso método findOne, passando um objeto de configuração, informando que queremos que ele faça o carregamento dos dados relacionados à essa tabela.
O método ficará assim:
// src/categorias/categorias.service.ts
...
findOne(id: number) {
return this.categoriaRepository.findOne(id, {
relations: ['videos'],
loadEagerRelations: true,
});
}
...
Agora sim, ao acessar nossa rota ".../categorias/1/videos", teremos nossos vídeos referentes à essa categoria.
O próximo card, tem a seguinte descrição:
Para atender a esse requisito, precisaremos alterar nosso controller e nosso service de videos:
// src/videos/videos.controller.ts
...
@Get()
findAll(@Query() query) {
return this.videosService.findAll(query.search);
}
...
// src/videos/videos.service.ts
...
findAll(search = '') {
return this.videoRepository.find({
where: { titulo: ILike(`%${search}%`) },
relations: ['categoria'],
});
}
...
Como já criamos uma categoria anteriormente, vamos enviar uma requisição de update para a categoria de ID 1, alterando o título para LIVRE
E após isso, vamos no videos.service e alteraremos a lógica do método create:
// src/videos/videos.service.ts
...
create(createVideoDto: CreateVideoDto) {
if (!createVideoDto.categoria)
return this.videoRepository.save({
...createVideoDto,
categoria: { id: 1 },
});
return this.videoRepository.save(createVideoDto);
}
...
Pronto, requisito atendido!
(Faça uma requisição criando um vídeo sem informar a categoria e veja se está tudo funcionando como o esperado, o retorno deve ser o vídeo criado e com a "categoria":{"id": 1})
O próximo requisito é para que se crie os testes automatizados e aí que o negócio começa a ficar legal.
Implementando testes automatizados
Para facilitar nossos mocks, vamos inserir um construtor para as nossas entidades, deixando elas assim:
// src/categorias/entities/categoria.entity.ts
...
constructor(private categoria?: Partial<Categoria>) {}
// src/categorias/entities/categoria.entity.ts
...
constructor(private video?: Partial<Categoria>) {}
Vamos criar também uma pasta common dentro de src e nela criar uma pasta test, que ficarão os arquivos necessário e comum à todos os testes. Por agora, teremos dois stubs:
videos.stub.ts (crie esse arquivo)
// src/common/test/videos.stub.ts
import { Categoria } from '../../categorias/entities/categoria.entity';
import { Video } from '../../videos/entities/video.entity';
import { categoriasStub } from './categorias.stub';
export const videosStub: Video[] = [
new Video({
id: 1,
titulo: 'título qualquer',
descricao: 'descrição qualquer',
url: 'http://url_qualquer.com',
categoria: new Categoria({ id: 1, titulo: 'LIVRE', cor: 'verde' }),
}),
new Video({
id: 2,
titulo: 'outro título qualquer',
descricao: 'outra descrição qualquer',
url: 'http://outra_url_qualquer.com',
categoria: categoriasStub[1],
}),
new Video({
id: 3,
titulo: 'titulo qualquer',
descricao: 'descrição qualquer',
url: 'http://url_qualquer.com',
categoria: categoriasStub[1],
}),
];
e categorias.stub.ts (crie também)
// src/common/test/categorias.stub.ts
import { Categoria } from '../../categorias/entities/categoria.entity';
export const categoriasStub: Categoria[] = [
new Categoria({ id: 1, titulo: 'LIVRE', cor: 'verde' }),
new Categoria({ id: 2, titulo: 'Programação', cor: 'azul' }),
];
Testando os controllers
Vamos rodas os testes e ver no que vai dar.
npm run test
Que delícia, nenhum teste passou!
Isso acontece pois alteramos e implementamos tudo sem criar nenhum teste o que não é muito legal, mas enfim, vamos começar a ajustar as coisa...
Testando nosso categorias.controller
O Nestjs já deixa uma estrutura pré-montada, então, vamos até o nosso arquivo categorias.controller.spec.ts para trabalhar nele.
Nosso controller tem como dependência o service e para que ele seja disponibilizado no teste precisamos prover ele.
Faremos dessa forma:
// src/categorias/categorias.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { categoriasStub } from '../common/test/categorias.stub';
import { CategoriasController } from './categorias.controller';
import { CategoriasService } from './categorias.service';
import { videosStub } from '../common/test/videos.stub';
describe('CategoriasController', () => {
let controller: CategoriasController;
let service: CategoriasService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CategoriasController],
providers: [
CategoriasService,
{
provide: CategoriasService,
useValue: {
create: jest.fn().mockResolvedValue(categoriasStub[0]),
findAll: jest.fn().mockResolvedValue(categoriasStub),
findOne: jest.fn().mockResolvedValue(categoriasStub[0]),
findVideoByCategory: jest.fn().mockResolvedValue(videosStub),
update: jest.fn().mockResolvedValue(categoriasStub[0]),
remove: jest.fn().mockResolvedValue(categoriasStub[0]),
},
},
],
}).compile();
controller = module.get<CategoriasController>(CategoriasController);
service = module.get<CategoriasService>(CategoriasService);
});
it('should be defined', () => {
expect(controller).toBeDefined();
expect(service).toBeDefined();
});
});
repare que nós criamos um mock para cada função que temos no serviço e definimos o retorno delas com nosso stub da categoria.
com isso, podemos rodar novamente o comando de testes do npm, só que agora, deixaremos ele em modo watch para que ele fique observando as alterações, enquanto implementamos os testes, mas faremos somente para o arquivo que estamos trabalhando no momento:
npm run test:watch -t /home/gabriel/Documentos/alura-challenges-2/src/categorias/categorias.controller.spec.ts
# substitua essa parte '/home/gabriel/Documentos' pelo caminho do seu computador, é claro.
Feito isso, o teste vai rodar e faremos a implementação do restante.
No final, esse arquivo ficará assim:
// src/categorias/categorias.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { categoriasStub } from '../common/test/categorias.stub';
import { CategoriasController } from './categorias.controller';
import { CategoriasService } from './categorias.service';
import { videosStub } from '../common/test/videos.stub';
describe('CategoriasController', () => {
let controller: CategoriasController;
let service: CategoriasService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CategoriasController],
providers: [
CategoriasService,
{
provide: CategoriasService,
useValue: {
create: jest.fn().mockResolvedValue(categoriasStub[0]),
findAll: jest.fn().mockResolvedValue(categoriasStub),
findOne: jest.fn().mockResolvedValue(categoriasStub[0]),
findVideoByCategory: jest.fn().mockResolvedValue(videosStub),
update: jest.fn().mockResolvedValue(categoriasStub[0]),
remove: jest.fn().mockResolvedValue(categoriasStub[0]),
},
},
],
}).compile();
controller = module.get<CategoriasController>(CategoriasController);
service = module.get<CategoriasService>(CategoriasService);
});
it('should be defined', () => {
expect(controller).toBeDefined();
expect(service).toBeDefined();
});
describe('create', () => {
it('should create a category', async () => {
const result = await service.create(categoriasStub[0]);
expect(result).toEqual(categoriasStub[0]);
expect(service.create).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'create').mockRejectedValueOnce(new Error());
expect(service.create(categoriasStub[0])).rejects.toThrowError();
expect(service.create).toHaveBeenCalledTimes(1);
});
});
describe('findAll', () => {
it('should return a category list', async () => {
const result = await service.findAll();
expect(result).toEqual(categoriasStub);
expect(service.findAll).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'findAll').mockRejectedValueOnce(new Error());
expect(service.findAll()).rejects.toThrowError();
expect(service.findAll).toHaveBeenCalledTimes(1);
});
});
describe('findOne', () => {
it('should return a category', async () => {
const result = await service.findOne(categoriasStub[0].id);
expect(result).toEqual(categoriasStub[0]);
expect(service.findOne).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'findOne').mockRejectedValueOnce(new Error());
expect(service.findOne(1)).rejects.toThrowError();
expect(service.findOne).toHaveBeenCalledTimes(1);
});
});
describe('findVideosByCategoryId', () => {
it('should return videos from a category', async () => {
const result = await service.findVideoByCategory(categoriasStub[0].id);
expect(result).toEqual(videosStub);
expect(service.findVideoByCategory).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest
.spyOn(service, 'findVideoByCategory')
.mockRejectedValueOnce(new Error());
expect(service.findVideoByCategory(1)).rejects.toThrowError();
expect(service.findVideoByCategory).toHaveBeenCalledTimes(1);
});
});
describe('update', () => {
it('should return a updated category', async () => {
const result = await service.update(
categoriasStub[0].id,
categoriasStub[0],
);
expect(result).toEqual(categoriasStub[0]);
expect(service.update).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'update').mockRejectedValueOnce(new Error());
expect(service.update(1, categoriasStub[0])).rejects.toThrowError();
expect(service.update).toHaveBeenCalledTimes(1);
});
});
describe('remove', () => {
it('should return a removed category', async () => {
const result = await service.remove(categoriasStub[0].id);
expect(result).toEqual(categoriasStub[0]);
expect(service.remove).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'remove').mockRejectedValueOnce(new Error());
expect(service.remove(1)).rejects.toThrowError();
expect(service.remove).toHaveBeenCalledTimes(1);
});
});
});
Passou todos os testes? vamos para o próximo!
Testando nosso videos.controller
npm run test:watch -t /home/gabriel/Documentos/alura-challenges-2/src/videos/videos.controller.spec.ts
# substitua essa parte '/home/gabriel/Documentos' pelo caminho do seu computador, é claro.
A ideia é a mesma para cá, então, esse arquivo fica assim:
// src/videos/videos.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { VideosController } from './videos.controller';
import { VideosService } from './videos.service';
import { videosStub } from '../common/test/videos.stub';
describe('VideosController', () => {
let controller: VideosController;
let service: VideosService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [VideosController],
providers: [
VideosService,
{
provide: VideosService,
useValue: {
create: jest.fn().mockResolvedValue(videosStub[0]),
findAll: jest.fn().mockResolvedValue(videosStub),
findOne: jest.fn().mockResolvedValue(videosStub[0]),
update: jest.fn().mockResolvedValue(videosStub[0]),
remove: jest.fn().mockResolvedValue(videosStub[0]),
},
},
],
}).compile();
controller = module.get<VideosController>(VideosController);
service = module.get<VideosService>(VideosService);
});
it('should be defined', () => {
expect(controller).toBeDefined();
expect(service).toBeDefined();
});
describe('create', () => {
it('should create a video', async () => {
const result = await service.create(videosStub[0]);
expect(result).toEqual(videosStub[0]);
expect(service.create).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'create').mockRejectedValueOnce(new Error());
expect(service.create(videosStub[0])).rejects.toThrowError();
expect(service.create).toHaveBeenCalledTimes(1);
});
});
describe('findAll', () => {
it('should return a video list', async () => {
const result = await service.findAll();
expect(result).toEqual(videosStub);
expect(service.findAll).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'findAll').mockRejectedValueOnce(new Error());
expect(service.findAll()).rejects.toThrowError();
expect(service.findAll).toHaveBeenCalledTimes(1);
});
});
describe('findOne', () => {
it('should return a video', async () => {
const result = await service.findOne(videosStub[0].id);
expect(result).toEqual(videosStub[0]);
expect(service.findOne).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'findOne').mockRejectedValueOnce(new Error());
expect(service.findOne(1)).rejects.toThrowError();
expect(service.findOne).toHaveBeenCalledTimes(1);
});
});
describe('update', () => {
it('should return a updated video', async () => {
const result = await service.update(videosStub[0].id, videosStub[0]);
expect(result).toEqual(videosStub[0]);
expect(service.update).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'update').mockRejectedValueOnce(new Error());
expect(service.update(1, videosStub[0])).rejects.toThrowError();
expect(service.update).toHaveBeenCalledTimes(1);
});
});
describe('remove', () => {
it('should return a removed video', async () => {
const result = await service.remove(videosStub[0].id);
expect(result).toEqual(videosStub[0]);
expect(service.remove).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(service, 'remove').mockRejectedValueOnce(new Error());
expect(service.remove(1)).rejects.toThrowError();
expect(service.remove).toHaveBeenCalledTimes(1);
});
});
});
Testes dos controllers ok, vamos para os services.
Testando os services
Sem muitas mudanças para cá também, faremos o mesmo que nos controllers, exceto pelo fato de que a dependência deixa de ser o serviço (óbvio, pois estamos no serviço rs) e passa a ser o nosso repository.
Trecho para prestar atenção:
// src/categorias/categorias.service.spec.ts
...
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CategoriasService,
{
provide: getRepositoryToken(Categoria),
useValue: {
save: jest.fn().mockResolvedValue(categoriasStub[0]),
find: jest.fn().mockResolvedValue(categoriasStub),
findOne: jest.fn().mockResolvedValue(categoriasStub[0]),
update: jest.fn().mockResolvedValue(categoriasStub[0]),
remove: jest.fn().mockResolvedValue(categoriasStub[0]),
},
},
],
}).compile();
service = module.get<CategoriasService>(CategoriasService);
repository = module.get(getRepositoryToken(Categoria));
});
...
Dada a devida atenção para o que muda dos controllers, vamos para as implementações dos services.
Testando nosso categorias.service
npm run test:watch -t /home/gabriel/Documentos/alura-challenges-2/src/categorias/categorias.service.spec.ts
# substitua essa parte '/home/gabriel/Documentos' pelo caminho do seu computador, é claro.
O código para testar nosso service de categorias ficará assim:
// src/categorias/categorias.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CategoriasService } from './categorias.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Categoria } from './entities/categoria.entity';
import { categoriasStub } from '../common/test/categorias.stub';
import { Repository } from 'typeorm';
describe('CategoriasService', () => {
let service: CategoriasService;
let repository: Repository<Categoria>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CategoriasService,
{
provide: getRepositoryToken(Categoria),
useValue: {
save: jest.fn().mockResolvedValue(categoriasStub[0]),
find: jest.fn().mockResolvedValue(categoriasStub),
findOne: jest.fn().mockResolvedValue(categoriasStub[0]),
update: jest.fn().mockResolvedValue(categoriasStub[0]),
remove: jest.fn().mockResolvedValue(categoriasStub[0]),
},
},
],
}).compile();
service = module.get<CategoriasService>(CategoriasService);
repository = module.get(getRepositoryToken(Categoria));
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(repository).toBeDefined();
});
describe('save', () => {
it('should create a category', async () => {
const newCategory: Omit<Categoria, 'id'> = categoriasStub[0];
const result = await service.create(newCategory);
expect(result).toEqual(categoriasStub[0]);
expect(repository.save).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'save').mockRejectedValueOnce(new Error());
expect(service.create(categoriasStub[0])).rejects.toThrowError();
expect(repository.save).toHaveBeenCalledTimes(1);
});
});
describe('findAll', () => {
it('should return a categories list', async () => {
const result = await service.findAll();
expect(result).toEqual(categoriasStub);
expect(repository.find).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'find').mockRejectedValueOnce(new Error());
expect(service.findAll()).rejects.toThrowError();
expect(repository.find).toHaveBeenCalledTimes(1);
});
});
describe('findOne', () => {
it('should return a category', async () => {
const result = await service.findOne(categoriasStub[0].id);
expect(result).toEqual(categoriasStub[0]);
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'findOne').mockRejectedValueOnce(new Error());
expect(service.findOne(1)).rejects.toThrowError();
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
});
describe('update', () => {
it('should return a updated category', async () => {
const result = await service.update(
categoriasStub[0].id,
categoriasStub[0],
);
expect(result).toEqual(categoriasStub[0]);
expect(repository.update).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'update').mockRejectedValueOnce(new Error());
expect(service.update(1, categoriasStub[0])).rejects.toThrowError();
expect(repository.update).toHaveBeenCalledTimes(1);
});
});
describe('remove', () => {
it('should return a removed category', async () => {
const result = await service.remove(categoriasStub[0].id);
expect(result).toEqual(categoriasStub[0]);
expect(repository.remove).toHaveBeenCalledTimes(1);
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'remove').mockRejectedValueOnce(new Error());
expect(service.remove(1)).rejects.toThrowError();
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
});
});
Testando nosso videos.service
E para o nossos testes do service de videos:
npm run test:watch -t /home/gabriel/Documentos/alura-challenges-2/src/videos/videos.service.spec.ts
# substitua essa parte '/home/gabriel/Documentos' pelo caminho do seu computador, é claro.
// src/videos/videos.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { videosStub } from '../common/test/videos.stub';
import { Video } from './entities/video.entity';
import { VideosService } from './videos.service';
describe('VideosService', () => {
let service: VideosService;
let repository: Repository<Video>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
VideosService,
{
provide: getRepositoryToken(Video),
useValue: {
save: jest.fn().mockResolvedValue(videosStub[0]),
find: jest.fn().mockResolvedValue(videosStub),
findOne: jest.fn().mockResolvedValue(videosStub[0]),
update: jest.fn().mockResolvedValue(videosStub[0]),
remove: jest.fn().mockResolvedValue(videosStub[0]),
},
},
],
}).compile();
service = module.get<VideosService>(VideosService);
repository = module.get(getRepositoryToken(Video));
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(repository).toBeDefined();
});
describe('save', () => {
const newVideo: Omit<Video, 'id'> = videosStub[0];
it('should create a video', async () => {
const result = await service.create(newVideo);
expect(result).toEqual(videosStub[0]);
expect(repository.save).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'save').mockRejectedValueOnce(new Error());
expect(service.create(videosStub[0])).rejects.toThrowError();
expect(repository.save).toHaveBeenCalledTimes(1);
});
});
describe('findAll', () => {
it('should return a videos list if search is not informed', async () => {
const result = await service.findAll();
expect(result).toEqual(videosStub);
expect(repository.find).toHaveBeenCalledTimes(1);
});
it('should return a videos list if search is informed', async () => {
//Arrange
const expectedResult = videosStub.filter((video) =>
video.titulo?.includes('teste'),
);
jest.spyOn(repository, 'find').mockResolvedValue(expectedResult);
//Act
const result = await service.findAll('teste');
//Assert
expect(result).toEqual([]);
expect(repository.find).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'find').mockRejectedValueOnce(new Error());
expect(service.findAll()).rejects.toThrowError();
expect(repository.find).toHaveBeenCalledTimes(1);
});
});
describe('findOne', () => {
it('should return a video', async () => {
const result = await service.findOne(videosStub[0].id);
expect(result).toEqual(videosStub[0]);
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'findOne').mockRejectedValueOnce(new Error());
expect(service.findOne(1)).rejects.toThrowError();
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
});
describe('update', () => {
it('should return a updated video', async () => {
const result = await service.update(videosStub[0].id, videosStub[0]);
expect(result).toEqual(videosStub[0]);
expect(repository.update).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'update').mockRejectedValueOnce(new Error());
expect(service.update(1, videosStub[0])).rejects.toThrowError();
expect(repository.update).toHaveBeenCalledTimes(1);
});
});
describe('remove', () => {
it('should return a removed video', async () => {
const result = await service.remove(videosStub[0].id);
expect(result).toEqual(videosStub[0]);
expect(repository.remove).toHaveBeenCalledTimes(1);
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
it('should throw an exception', () => {
jest.spyOn(repository, 'remove').mockRejectedValueOnce(new Error());
expect(service.remove(1)).rejects.toThrowError();
expect(repository.findOne).toHaveBeenCalledTimes(1);
});
});
});
E com isso, fechamos as implementações da segunda semana.
Aaaah, estou fazendo meus commits conforme vou implementando as coisas... (Padronizadinho, conforme configuramos no início)
Está lá no meu Github.
Abraços e até a próxima semana!
Posted on September 1, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.