DEV Community

joedev090
joedev090

Posted on

Create API with NestJS using TDD approach Part two

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

  1. 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 implement

  2. Write Minimal Code to Pass the Test:
    The next step is to write the minimal amount of code necessary to make the test pass

  3. Refactor 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);
    });
  });


Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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));
  });
Enter fullscreen mode Exit fullscreen mode

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);
    });
  });
Enter fullscreen mode Exit fullscreen mode

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) {}
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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;
    }
Enter fullscreen mode Exit fullscreen mode

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);
    })
  })
Enter fullscreen mode Exit fullscreen mode

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);
  });
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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);
    });
  });
Enter fullscreen mode Exit fullscreen mode

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 }),
      ),
    };
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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

https://github.com/joeaspiazudeveloper/nestjs-houselocation-tdd/tree/second-part-implementRestFuncTdd

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)