Hey Coders :)
We continue with the second part of this project using NestJS.
These are some concepts we were checking in the first part.
Core Concepts of TDD
Write a Failing Test:
The first step in the TDD cycle is to write a test for a small piece of functionality that you want to implementWrite Minimal Code to Pass the Test:
The next step is to write the minimal amount of code necessary to make the test passRefactor the Code:
Once the test is passing, the final step is to refactor the code to improve its structure and readability.
This cycle, often referred to as "Red-Green-Refactor," ensures that you always have a working version of your code and that all parts of your codebase are covered by tests.
Sooo, let's create the next test case for create property.
In properties/test/properties.service.spec.ts add this code:
describe('create', () => {
it('should successfully insert a property', async () => {
const createPropertyDto: CreatePropertyDto = {
name: 'New Property',
description: 'New Description',
location: 'New Location',
imageUrl: 'http://example.com/image.jpg',
unitsAvailable: 5,
hasWifi: true,
hasLaundry: false,
};
const result = await service.create(createPropertyDto);
expect(result).toBeDefined();
expect(result.name).toEqual(createPropertyDto.name);
expect(repository.save).toHaveBeenCalledWith(createPropertyDto);
});
});
You should see some errors, no worries!
We have to create a dto for createPropertyDTO, so let's do that.
First install this dependency with this command:
npm i class-validator
Create a new file in dto/create-property-dto.ts and add this code:
import { IsString, IsNumber, IsBoolean } from 'class-validator';
export class CreatePropertyDto {
@IsString()
name: string;
@IsString()
description: string;
@IsString()
location: string;
@IsString()
imageUrl: string;
@IsNumber()
unitsAvailable: number;
@IsBoolean()
hasWifi: boolean;
@IsBoolean()
hasLaundry: boolean;
}
Save the changes and run the tests with
npm run test
And you will have the error about the create function in the service. That's fine!!
Let's go to properties.service.ts and add this code at the end:
create(createPropertyDto: CreatePropertyDto): Promise<Property> {
const newProperty = this.propertiesRepository.create(createPropertyDto);
return this.propertiesRepository.save(newProperty);
}
Then let's run the tests again
npm run test
I can see other error, and it was about the mockImplementation of findOne, let's add this code in properties.service.spec.ts, replace the beforeEach
beforeEach(async () => {
const mockRepository = {
find: jest.fn().mockResolvedValue([]),
findOne: jest.fn().mockImplementation((options) =>
options.where.id === 999
? Promise.resolve(null)
: Promise.resolve({
id: 1,
name: 'Test Property',
description: 'Test Description',
location: 'Test Location',
imageUrl: 'http://example.com/image.jpg',
unitsAvailable: 5,
hasWifi: true,
hasLaundry: false,
}),
),
create: jest.fn().mockImplementation((dto) => dto),
save: jest
.fn()
.mockImplementation((property) =>
Promise.resolve({ id: 1, ...property }),
),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
PropertiesService,
{
provide: getRepositoryToken(Property),
useValue: mockRepository,
},
],
}).compile();
service = module.get<PropertiesService>(PropertiesService);
repository = module.get<Repository<Property>>(getRepositoryToken(Property));
});
Finally if you run again the tests, it should be passed!
The next step is to implement the Update funcionality...
As we expect, create the test for the update in src/properties/test/properties.service.spec.ts
// describe the update
describe('update', () => {
it('should update a property', async () => {
const updatePropertyDto: UpdatePropertyDto = {
name: 'Updated Property',
};
const mockProperty = new Property();
mockProperty.id = 1;
mockProperty.name = 'Acme Fresh Start Housing';
jest.spyOn(repository, 'findOne').mockResolvedValue(mockProperty);
jest.spyOn(repository, 'save').mockResolvedValue({ ...mockProperty, ...updatePropertyDto });
const result = await service.update(1, updatePropertyDto);
expect(result.name).toEqual(updatePropertyDto.name);
});
});
Then we run the command
npm run test
And it will fails because of the missing function.
Then let's create the function update in the service, but before doing it, we have to install a dependency and create a new file.
Run the command:
npm i @nestjs/mapped-types
After this let's create a new dtos
src/properties/dto/update-property.dto.ts with this code:
import { PartialType } from '@nestjs/mapped-types';
import { CreatePropertyDto } from './create-property.dto';
export class UpdatePropertyDto extends PartialType(CreatePropertyDto) {}
And the final step for update, let's add the function in the service src/properties/properties.service.ts
async update(id: number, updatePropertyDto: UpdatePropertyDto): Promise<Property> {
const property = await this.findOne(id);
Object.assign(property, updatePropertyDto);
return this.propertiesRepository.save(property);
}
Also let's add this function at the top
async findOne(id: number): Promise<Property> {
const property = await this.propertiesRepository.findOne({ where: { id } });
if (!property) {
throw new NotFoundException(`Property with ID ${id} not found.`);
}
return property;
}
The FindOne is used in the update function.
Finally let's run the tests and it should pass.
After these steps we have implemented the create and update functionality for the service in our nestJS project.
The next step will have similar approach, creating first the tests and then create the function, but we will focus on the controller.
Go to src/properties/test/properties.controller.spec.ts
In this case we will add the tests for creating property
describe('create', () => {
it('should create a property', async () => {
const createPropertyDto: CreatePropertyDto = {
name: 'New Property',
description: 'New Description',
location: 'New Location',
imageUrl: 'http://example.com/image.jpg',
unitsAvailable: 5,
hasWifi: true,
hasLaundry: false,
};
const result = await controller.create(createPropertyDto);
expect(result).toBeDefined();
expect(result.name).toEqual(createPropertyDto.name);
expect(service.create).toHaveBeenCalledWith(createPropertyDto);
})
})
Also we have to add some additional code in this file:
The main describe is edited with findAll and create in the mockPropertiesService
describe('PropertiesController', () => {
let controller: PropertiesController;
let service: PropertiesService;
beforeEach(async () => {
const mockPropertiesService = {
findAll: jest.fn().mockResolvedValue([]),
create: jest.fn().mockImplementation((createPropertyDto) =>
Promise.resolve({ id: Date.now(), ...createPropertyDto })
),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [PropertiesController],
providers: [
{
provide: PropertiesService,
useValue: mockPropertiesService, // Use the mock service with the create method
},
],
}).compile();
controller = module.get<PropertiesController>(PropertiesController);
service = module.get<PropertiesService>(PropertiesService);
});
If you can see any other issue, feel free to check the repository code on github link at the end of the post.
As part of TDD we run the tests and it will fail.
npm run test
We are missing the functionality of create and findOne
Let's add the next code in
src/properties/properties.controller.ts
@Get(':id')
findOne(@Param('id') id: number): Promise<Property> {
return this.propertiesService.findOne(id);
}
@Post()
create(@Body() createPropertyDto: CreatePropertyDto): Promise<Property> {
return this.propertiesService.create(createPropertyDto);
}
Please the imports necessary to prevent errors, Here we have the import section of this file
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { PropertiesService } from './properties.service';
import { Property } from './entities/property.entity';
import { CreatePropertyDto } from './dto/create-property.dto';
After adding the functionality in property service of create and findOne
We will run the tests
npm run test
and Yeyyyy!! it should work passed all.
Let's apply this same approach with the update function to the controller.
First let's create the minimal tests in properties.controller.spec.ts
// update
describe('update', () => {
it('should update a property', async () => {
const updatePropertyDto: UpdatePropertyDto = {
name: 'Updated Property',
};
const result = await controller.update(1, updatePropertyDto);
expect(result).toBeDefined();
expect(result.id).toEqual(1);
expect(result.name).toEqual(updatePropertyDto.name);
expect(service.update).toHaveBeenCalledWith(1, updatePropertyDto);
});
});
Also we have to add in the beforeEach the update parameter in the mockedPropertiesService, adding this code
beforeEach(async () => {
const mockPropertiesService = {
findAll: jest.fn().mockResolvedValue([]),
create: jest.fn().mockImplementation((createPropertyDto) =>
Promise.resolve({ id: Date.now(), ...createPropertyDto })
),
update: jest.fn().mockImplementation((id: number, updatePropertyDto: UpdatePropertyDto) =>
Promise.resolve({ id, ...updatePropertyDto }),
),
};
Run the tests,
npm run test
And then we have to add in the controller the update function.
@Put(':id')
async update(
@Param('id') id: number,
@Body() updatePropertyDto: UpdatePropertyDto,
): Promise<Property> {
return this.propertiesService.update(id, updatePropertyDto);
}
Don't forget to add the necessary imports and if you have some issue, please check the repository on my github.
After we add the update funcion, we should run the tests again
npm run test
And there you go
You should see a screen similar to this!!
It has been a large article, I know, it could be tedious at the beginning but Believe me!! Let's program with this approach and you have the advantage as software engineer!!
See you coders.
This is the link for the repositoy of this second part!!
And keep in touch, because in the last part we will finish adding the remove functionality and also we will learn how to add swagger in our nestjs app!!
Chaooooo coders!
Top comments (0)