Skip to content

Commit a542f67

Browse files
committed
Added tests
1 parent c9bc873 commit a542f67

10 files changed

+627
-162
lines changed

apps/car-rental-backend/src/common/pages/dto/page-options.dto.ts

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { Order } from '../../constants';
44
import { IsEnum, IsInt, IsOptional, Max, Min } from 'class-validator';
55

66
export class PageOptionsDto {
7+
@ApiPropertyOptional()
8+
@Type(() => String)
9+
@IsOptional()
10+
readonly sortField?: string;
711
@ApiPropertyOptional({ enum: Order, default: Order.ASC })
812
@IsEnum(Order)
913
@IsOptional()

apps/car-rental-backend/src/vehicle-brands/vehicle-brands.controller.spec.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('VehicleBrandsController', () => {
2222
findVehicleBrands: jest.fn(),
2323
findOne: jest.fn(),
2424
update: jest.fn(),
25+
remove: jest.fn(),
2526
},
2627
},
2728
],
@@ -31,10 +32,6 @@ describe('VehicleBrandsController', () => {
3132
service = module.get<VehicleBrandsService>(VehicleBrandsService);
3233
});
3334

34-
it('should be defined', () => {
35-
expect(controller).toBeDefined();
36-
});
37-
3835
describe('create', () => {
3936
it('should call service.create with correct parameters', async () => {
4037
const createVehicleBrandDto: CreateVehicleBrandDto = { name: 'Toyota' };
@@ -85,4 +82,14 @@ describe('VehicleBrandsController', () => {
8582
expect(service.update).toHaveBeenCalledWith(id, updateVehicleBrandDto);
8683
});
8784
});
85+
86+
describe('remove', () => {
87+
it('should call service.remove with correct parameters', async () => {
88+
const id = 1;
89+
jest.spyOn(service, 'remove').mockResolvedValue(undefined);
90+
91+
expect(await controller.remove(id)).toBeUndefined();
92+
expect(service.remove).toHaveBeenCalledWith(id);
93+
});
94+
});
8895
});

apps/car-rental-backend/src/vehicle-brands/vehicle-brands.service.spec.ts

+162-26
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { VehicleBrandsService } from './vehicle-brands.service';
33
import { getRepositoryToken } from '@nestjs/typeorm';
4-
import { Repository } from 'typeorm';
4+
import { Repository, SelectQueryBuilder } from 'typeorm';
55
import { HttpException, HttpStatus } from '@nestjs/common';
66
import { VehicleBrand } from './entities/vehicle-brand.entity';
7+
import { Order } from '../common/constants';
78

89
describe('VehicleBrandsService', () => {
910
let service: VehicleBrandsService;
@@ -26,6 +27,116 @@ describe('VehicleBrandsService', () => {
2627
);
2728
});
2829

30+
describe('create', () => {
31+
it('should create a vehicle brand if it does not exist', async () => {
32+
const createVehicleBrandDto = { name: 'Toyota' };
33+
const vehicleBrand = { id: 1, name: 'Toyota' } as VehicleBrand;
34+
jest.spyOn(repository, 'find').mockResolvedValue([]);
35+
jest.spyOn(repository, 'save').mockResolvedValue(vehicleBrand);
36+
37+
expect(await service.create(createVehicleBrandDto)).toEqual(vehicleBrand);
38+
expect(repository.find).toHaveBeenCalledWith({
39+
where: { name: 'Toyota' },
40+
});
41+
expect(repository.save).toHaveBeenCalledWith(createVehicleBrandDto);
42+
});
43+
44+
it('should throw an exception if vehicle brand already exists', async () => {
45+
const createVehicleBrandDto = { name: 'Toyota' };
46+
jest
47+
.spyOn(repository, 'find')
48+
.mockResolvedValue([{ id: 1, name: 'Toyota' } as VehicleBrand]);
49+
50+
await expect(service.create(createVehicleBrandDto)).rejects.toThrow(
51+
new HttpException(
52+
{
53+
status: HttpStatus.CONFLICT,
54+
error: 'Vehicle brand already exists',
55+
},
56+
HttpStatus.CONFLICT
57+
)
58+
);
59+
expect(repository.find).toHaveBeenCalledWith({
60+
where: { name: 'Toyota' },
61+
});
62+
});
63+
});
64+
65+
describe('findVehicleBrands', () => {
66+
it('should return a list of vehicle brands', async () => {
67+
const pageOptionsDto = {
68+
order: Order.ASC,
69+
take: 10,
70+
skip: 0,
71+
sortField: 'name',
72+
};
73+
const vehicleBrands = [
74+
{ id: 2, name: 'Mercedes' } as VehicleBrand,
75+
{ id: 1, name: 'Toyota' } as VehicleBrand,
76+
];
77+
jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
78+
orderBy: jest.fn().mockReturnThis(),
79+
skip: jest.fn().mockReturnThis(),
80+
take: jest.fn().mockReturnThis(),
81+
getCount: jest.fn().mockResolvedValue(2),
82+
getRawAndEntities: jest
83+
.fn()
84+
.mockResolvedValue({ entities: vehicleBrands }),
85+
} as unknown as SelectQueryBuilder<VehicleBrand>);
86+
87+
const result = await service.findVehicleBrands(pageOptionsDto);
88+
expect(result.data[0]).toEqual(vehicleBrands[0]);
89+
expect(result.data[1]).toEqual(vehicleBrands[1]);
90+
expect(result.meta.itemCount).toEqual(2);
91+
});
92+
93+
it('should return a list of vehicle brands sorted by id if sortField is not provided', async () => {
94+
const pageOptionsDto = {
95+
order: Order.ASC,
96+
take: 10,
97+
skip: 0,
98+
};
99+
const vehicleBrands = [
100+
{ id: 1, name: 'Toyota' } as VehicleBrand,
101+
{ id: 2, name: 'Mercedes' } as VehicleBrand,
102+
];
103+
jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
104+
orderBy: jest.fn().mockReturnThis(),
105+
skip: jest.fn().mockReturnThis(),
106+
take: jest.fn().mockReturnThis(),
107+
getCount: jest.fn().mockResolvedValue(2),
108+
getRawAndEntities: jest
109+
.fn()
110+
.mockResolvedValue({ entities: vehicleBrands }),
111+
} as unknown as SelectQueryBuilder<VehicleBrand>);
112+
113+
const result = await service.findVehicleBrands(pageOptionsDto);
114+
expect(result.data[0]).toEqual(vehicleBrands[0]);
115+
expect(result.data[1]).toEqual(vehicleBrands[1]);
116+
expect(result.meta.itemCount).toEqual(2);
117+
});
118+
119+
it('should return an empty list if no vehicle brands found', async () => {
120+
const pageOptionsDto = {
121+
order: Order.ASC,
122+
take: 10,
123+
skip: 0,
124+
sortField: 'name',
125+
};
126+
jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
127+
orderBy: jest.fn().mockReturnThis(),
128+
skip: jest.fn().mockReturnThis(),
129+
take: jest.fn().mockReturnThis(),
130+
getCount: jest.fn().mockResolvedValue(0),
131+
getRawAndEntities: jest.fn().mockResolvedValue({ entities: [] }),
132+
} as unknown as SelectQueryBuilder<VehicleBrand>);
133+
134+
const result = await service.findVehicleBrands(pageOptionsDto);
135+
expect(result.data).toEqual([]);
136+
expect(result.meta.itemCount).toEqual(0);
137+
});
138+
});
139+
29140
describe('findByField', () => {
30141
it('should return a vehicle brand if found', async () => {
31142
const vehicleBrand = { id: 1, name: 'Toyota' } as VehicleBrand;
@@ -49,6 +160,7 @@ describe('VehicleBrandsService', () => {
49160
describe('findOne', () => {
50161
it('should call findByField with id', async () => {
51162
const vehicleBrand = { id: 1, name: 'Toyota' };
163+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
52164
jest.spyOn(service as any, 'findByField').mockResolvedValue(vehicleBrand);
53165

54166
expect(await service.findOne(1)).toEqual(vehicleBrand);
@@ -59,6 +171,7 @@ describe('VehicleBrandsService', () => {
59171
describe('findByName', () => {
60172
it('should call findByField with name', async () => {
61173
const vehicleBrand = { id: 1, name: 'Toyota' };
174+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
62175
jest.spyOn(service as any, 'findByField').mockResolvedValue(vehicleBrand);
63176

64177
expect(await service.findByName('Toyota')).toEqual(vehicleBrand);
@@ -71,14 +184,7 @@ describe('VehicleBrandsService', () => {
71184
const vehicleBrand = { id: 1, name: 'Toyota' } as VehicleBrand;
72185
const updateVehicleBrandDto = { name: 'Mercedes' };
73186
const updatedVehicleBrand = { id: 1, name: 'Mercedes' } as VehicleBrand;
74-
jest
75-
.spyOn(service, 'findByName')
76-
.mockRejectedValue(
77-
new HttpException(
78-
{ status: HttpStatus.NOT_FOUND, error: 'Vehicle brand not found' },
79-
HttpStatus.NOT_FOUND
80-
)
81-
);
187+
jest.spyOn(repository, 'find').mockResolvedValue([]);
82188
jest
83189
.spyOn(service, 'findOne')
84190
.mockResolvedValueOnce(vehicleBrand)
@@ -88,7 +194,9 @@ describe('VehicleBrandsService', () => {
88194
expect(await service.update(1, updateVehicleBrandDto)).toEqual(
89195
updatedVehicleBrand
90196
);
91-
expect(service.findByName).toHaveBeenCalledWith(updatedVehicleBrand.name);
197+
expect(repository.find).toHaveBeenCalledWith({
198+
where: { name: updatedVehicleBrand.name },
199+
});
92200
expect(service.findOne).toHaveBeenCalledWith(vehicleBrand.id);
93201
expect(repository.save).toHaveBeenCalledWith({
94202
...vehicleBrand,
@@ -98,14 +206,7 @@ describe('VehicleBrandsService', () => {
98206

99207
it('should throw an exception if vehicle brand not found', async () => {
100208
const updateVehicleBrandDto = { name: 'Toyota' };
101-
jest
102-
.spyOn(service, 'findByName')
103-
.mockRejectedValue(
104-
new HttpException(
105-
{ status: HttpStatus.NOT_FOUND, error: 'Vehicle brand not found' },
106-
HttpStatus.NOT_FOUND
107-
)
108-
);
209+
jest.spyOn(repository, 'find').mockResolvedValue([]);
109210

110211
jest
111212
.spyOn(service, 'findOne')
@@ -122,17 +223,17 @@ describe('VehicleBrandsService', () => {
122223
HttpStatus.NOT_FOUND
123224
)
124225
);
125-
expect(service.findByName).toHaveBeenCalledWith(
126-
updateVehicleBrandDto.name
127-
);
226+
expect(repository.find).toHaveBeenCalledWith({
227+
where: { name: updateVehicleBrandDto.name },
228+
});
128229
expect(service.findOne).toHaveBeenCalledWith(1);
129230
});
130231

131232
it('should throw an exception if vehicle brand to be updated already exists', async () => {
132233
const updateVehicleBrandDto = { name: 'Toyota' };
133234
jest
134-
.spyOn(service, 'findByName')
135-
.mockResolvedValue({ id: 2, name: 'Toyota' } as VehicleBrand);
235+
.spyOn(repository, 'find')
236+
.mockResolvedValue([{ id: 2, name: 'Toyota' } as VehicleBrand]);
136237

137238
jest
138239
.spyOn(service, 'findOne')
@@ -147,10 +248,45 @@ describe('VehicleBrandsService', () => {
147248
HttpStatus.CONFLICT
148249
)
149250
);
150-
expect(service.findByName).toHaveBeenCalledWith(
151-
updateVehicleBrandDto.name
152-
);
251+
expect(repository.find).toHaveBeenCalledWith({
252+
where: { name: updateVehicleBrandDto.name },
253+
});
153254
expect(service.findOne).not.toHaveBeenCalledWith(1);
154255
});
155256
});
257+
258+
describe('remove', () => {
259+
it('should remove a vehicle brand if it exists', async () => {
260+
const vehicleBrand = { id: 1, name: 'Toyota' } as VehicleBrand;
261+
jest.spyOn(service, 'findOne').mockResolvedValue(vehicleBrand);
262+
jest
263+
.spyOn(repository, 'delete')
264+
.mockResolvedValue(undefined);
265+
266+
expect(await service.remove(1)).toEqual(undefined);
267+
expect(service.findOne).toHaveBeenCalledWith(1);
268+
expect(repository.delete).toHaveBeenCalledWith(1);
269+
});
270+
271+
it('should throw an exception if vehicle brand not found', async () => {
272+
jest
273+
.spyOn(service, 'findOne')
274+
.mockRejectedValue(
275+
new HttpException(
276+
{ status: HttpStatus.NOT_FOUND, error: 'Vehicle brand not found' },
277+
HttpStatus.NOT_FOUND
278+
)
279+
);
280+
jest.spyOn(repository, 'delete');
281+
282+
await expect(service.remove(1)).rejects.toThrow(
283+
new HttpException(
284+
{ status: HttpStatus.NOT_FOUND, error: 'Vehicle brand not found' },
285+
HttpStatus.NOT_FOUND
286+
)
287+
);
288+
expect(service.findOne).toHaveBeenCalledWith(1);
289+
expect(repository.delete).not.toHaveBeenCalled();
290+
});
291+
});
156292
});

apps/car-rental-backend/src/vehicle-brands/vehicle-brands.service.ts

+23-22
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ export class VehicleBrandsService {
1818
private vehicleBrandsRepository: Repository<VehicleBrand>
1919
) {}
2020

21-
async create(createVehicleBrandDto: CreateVehicleBrandDto) {
21+
async create(createVehicleBrandDto: CreateVehicleBrandDto): Promise<VehicleBrandDto> {
2222
const { name } = createVehicleBrandDto;
23-
const vehicleBrand = await this.findByName(name);
24-
if (vehicleBrand) {
23+
const vehicleBrand = await this.vehicleBrandsRepository.find({
24+
where: { name },
25+
});
26+
if (vehicleBrand.length) {
2527
throw new HttpException(
2628
{ status: HttpStatus.CONFLICT, error: 'Vehicle brand already exists' },
2729
HttpStatus.CONFLICT
@@ -30,11 +32,15 @@ export class VehicleBrandsService {
3032
return this.vehicleBrandsRepository.save(createVehicleBrandDto);
3133
}
3234

33-
async findVehicleBrands(pageOptionsDto: PageOptionsDto) {
34-
const { order, take, skip } = pageOptionsDto;
35+
async findVehicleBrands(pageOptionsDto: PageOptionsDto): Promise<PageDto<VehicleBrandDto>> {
36+
const { order, take, skip, sortField } = pageOptionsDto;
3537
const queryBuilder =
3638
this.vehicleBrandsRepository.createQueryBuilder('vehicleBrand');
37-
queryBuilder.orderBy('vehicleBrand.name', order).skip(skip).take(take);
39+
const vehicleBrandSortField =
40+
sortField && sortField in VehicleBrandDto
41+
? `vehicleBrand.${sortField}`
42+
: 'vehicleBrand.id';
43+
queryBuilder.orderBy(vehicleBrandSortField, order).skip(skip).take(take);
3844
const itemCount = await queryBuilder.getCount();
3945
const { entities } = await queryBuilder.getRawAndEntities();
4046
const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
@@ -57,25 +63,20 @@ export class VehicleBrandsService {
5763
return vehicle;
5864
}
5965

60-
async findOne(id: number) {
66+
async findOne(id: number): Promise<VehicleBrandDto> {
6167
return this.findByField('id', id);
6268
}
6369

64-
async findByName(name: string) {
70+
async findByName(name: string): Promise<VehicleBrandDto> {
6571
return this.findByField('name', name);
6672
}
6773

68-
async update(id: number, updateVehicleBrandDto: UpdateVehicleBrandDto) {
74+
async update(id: number, updateVehicleBrandDto: UpdateVehicleBrandDto): Promise<VehicleBrandDto> {
6975
const { name } = updateVehicleBrandDto;
70-
let vehicleBrand: VehicleBrand;
71-
try {
72-
vehicleBrand = await this.findByName(name);
73-
} catch (error) {
74-
if (error.status !== HttpStatus.NOT_FOUND) {
75-
throw error;
76-
}
77-
}
78-
if (vehicleBrand) {
76+
const vehicleBrand = await this.vehicleBrandsRepository.find({
77+
where: { name },
78+
});
79+
if (vehicleBrand.length) {
7980
throw new HttpException(
8081
{ status: HttpStatus.CONFLICT, error: 'Vehicle brand already exists' },
8182
HttpStatus.CONFLICT
@@ -89,17 +90,17 @@ export class VehicleBrandsService {
8990
);
9091
}
9192
await this.vehicleBrandsRepository.save({ id, name });
92-
return await this.findOne(id);
93+
return this.findOne(id);
9394
}
9495

95-
async remove(id: number) {
96-
const vehicle = await this.vehicleBrandsRepository.findOneBy({ id });
96+
async remove(id: number): Promise<void> {
97+
const vehicle = await this.findOne(id);
9798
if (!vehicle) {
9899
throw new HttpException(
99100
{ status: HttpStatus.NOT_FOUND, error: 'Vehicle brand not found' },
100101
HttpStatus.NOT_FOUND
101102
);
102103
}
103-
return this.vehicleBrandsRepository.delete(id);
104+
await this.vehicleBrandsRepository.delete(id);
104105
}
105106
}

0 commit comments

Comments
 (0)