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!
Top comments (0)