Tips for creating an effective unit test using Jest for this specific controller

I'm currently grappling with the task of unit testing my Controller in an express app, but I seem to be stuck. Here are the code files I've been working with:

// CreateUserController.ts
import { Request, Response } from "express";
import { CreateUserService } from "../services/CreateUserService";

class CreateUserController {
    async handle(request: Request, response: Response) {
        try {
            const { name, email, admin, password } = request.body;

            const createUserService = new CreateUserService();

            const user = await createUserService.execute({ name, email, admin, password });
            
            return response.send(user);
        } catch (error) {
            return response.send({ error });
        }
    }
}

export { CreateUserController };
// CreateUserService.ts
import { getCustomRepository } from "typeorm";
import { UsersRepositories } from "../repositories/UsersRepositories";
import { hash } from "bcryptjs";

interface IUserRequest {
    name: string;
    email: string;
    admin?: boolean;
    password: string;
}

class CreateUserService {
    async execute({ name, email, admin = false, password }: IUserRequest) {
            const usersRepository = getCustomRepository(UsersRepositories);

            if(!email) {
                throw new Error('Incorrect e-mail.');
            }

            const userAlreadyExists = await usersRepository.findOne({ email });

            if(userAlreadyExists) {
                throw new Error('User already exists.');
            }

            const passwordHash = await hash(password, 8);

            const user = usersRepository.create({ name, email, admin, password: passwordHash });

            const savedUser = await usersRepository.save(user);

            return savedUser;
    }
}

export { CreateUserService };
// Users.ts
import { Entity, PrimaryColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
import { Exclude } from "class-transformer";
import { v4 as uuid } from "uuid";

@Entity("users")
class User {

    @PrimaryColumn()
    readonly id: string;

    @Column()
    name: string;

    @Column()
    email: string;

    @Column()
    admin: boolean;

    @Exclude()
    @Column()
    password: string;

    @CreateDateColumn()
    created_at: Date;

    @UpdateDateColumn()
    updated_at: Date;

    constructor() {
        if (!this.id) {
            this.id = uuid();
        }
    }
}

export default User;
// UsersRepositories.ts
import { EntityRepository, Repository } from "typeorm";
import User from "../entities/User";

@EntityRepository(User)
class UsersRepositories extends Repository<User> {}

export { UsersRepositories };

I am keen on testing the CreateUserController, however, it appears that the current implementation may not be easily testable. Can anyone confirm?

// CreateUserController.unit.test.ts
import { CreateUserController } from "./CreateUserController";
import { getCustomRepository } from "typeorm";
import { UsersRepositories } from "../repositories/UsersRepositories";
import { hash } from "bcryptjs";
import { v4 as uuid } from "uuid";


describe('testando', () => {
    it('primeiro it', async () => {
        const userId = uuid();
        const userData = {
            name: 'Fulano',
            email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0e6b636f67624e6a206d6163">[email protected]</a>',
            password: '123456',
            admin: true
        };
        const returnUserData = {
            id: userId,
            name: 'Fulano',
            email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="71141c10181d31155f121e1e">[email protected]</a>',
            password: await hash('123456', 8),
            admin: true,
            created_at: new Date(),
            updated_at: new Date()
        };

        jest.spyOn(getCustomRepository(UsersRepositories), 'save').mockImplementation(async () => returnUserData);

        const user = await CreateUserController.handle(userData);
    });
});

Currently encountering some errors, but this is what I've managed to put together so far.

Answer №1

To ensure the controller's functionality, a unit test is necessary to validate that its dependencies are functioning correctly. In this particular scenario, the controller relies on a single dependency - CreateUserService.

When testing the controller, it is essential to consider scenarios where the service returns a user as expected or throws an unexpected exception. Additionally, one should verify that the .send function is invoked with the correct parameters.

While there may be numerous aspects to explain, further exploration and investigation are encouraged for a deeper understanding of the topic.

// CreateUserController.test.ts
import { mocked } from "ts-jest/utils";
import { Request, Response } from "express";
import { CreateUserController } from "./CreateUserController"
import { CreateUserService } from "./CreateUserService";

jest.mock('./CreateUserService'); // mock CreateUserService constructor

describe('CreateUserController', () => {
  // Test cases implementation goes here...
});

// Update to handle error responses in the controller
/*
    It will fail in the second case because I expect that the controller has to respond with status 500, to make it pass, let update your controller:
*/

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the reason for the shift to a different command-line interface when executing a node file?

I've been trying to set up a node.js server, but for some reason, it's not running my server file. Here is the code for the server file: const express = require('express'); const app = express(); const PORT = 3000; app.use(express.json( ...

Navigating Angular single page application routes within an ASP.NET Web API project

Developed an Angular single-page application with specific routes: /messages /messages/:id By using ng serve, I can navigate to these pages at URLs like localhost:4200/messages and localhost:4200/messages/:id After building the solution, I transferred t ...

Issue with MySql Post: "Please refer to the documentation that aligns with your MySQL server version to ensure correct syntax near '' at line 1."

I am attempting to create basic node.js with Express requests for a todoList (GET, POST, PUT, DELETE). The GET function is functioning properly, but the POST function is not working. I keep encountering the following error: "ER_PARSE_ERROR: You have ...

Ways to access UserProfile in a different Dialogio

For the implementation of a chatbot, I am utilizing Microsoft's Bot Builder framework. However, upon implementing an alternative path to the dialog flow, I noticed that the user's Profile references are getting lost. Here is the code snippet fr ...

Resizing files before or after uploading with Node.js

Currently, I am utilizing both Express and multer to handle file uploads, specifically for images. However, I am interested in resizing the image either before or after the upload process. After doing extensive research, I came across several packages that ...

Mastering the art of leveraging the raw middleware in the Express framework

I experimented with this block of code: //index.js const express = require("express") const app = express() app.post("/", express.raw(), (req, res) => { console.log(req.body) res.write(req.body.toString()) res.send() }) app.listen(4000) ...

Maintain authentication state in React using express-session

Struggling to maintain API login session in my React e-commerce app. Initially logged in successfully, but facing a challenge upon page refresh as the state resets and I appear as not logged in on the client-side. However, attempting to log in again trigge ...

Creating a structure within a stencil web component

In my current project, I am utilizing Stencil.js (typescript) and need to integrate this selectbox. Below is the code snippet: import { Component, h, JSX, Prop, Element } from '@stencil/core'; import Selectr from 'mobius1-selectr'; @ ...

Learn the process of effortlessly transferring user information to a MongoDB database

Using socket.io, I am broadcasting geolocation data (lat, lng) and updating the marker icon on a map every time a user connects to the app. When a user from Japan connects, their position is shared with me and vice versa. The issue arises when I only want ...

Creating React Components with TypeScript: Ensuring Typechecking in Class and Function Components

Want to ensure typechecking works when defining React Class/Function components in TypeScript? Struggling to find a comprehensive guide on how to do it correctly. I've managed to define the Props and State interfaces, but I'm unsure about how to ...

In Angular, I aim to invoke the this.addDispatchToReceive method whenever the outcome is successful within each forEach iteration

How can I ensure that the values from this.stockItemDispatch are obtained in this.addDispatchToReceive(); during each iteration of a loop, instead of only getting the last stock value? The issue is that the subscribe function runs after the foreach cycle ...

What is the best way to transfer data from a component to a .ts file that contains an array?

I am currently developing a budgeting application and I have a component that is responsible for holding values that I want to pass to an array stored in a separate file. While I can retrieve data from the array, I am facing difficulty in figuring out how ...

The combination of Stripe, Angular, and TypeScript is not compatible

Attempting to utilize Stripe.card.createToken() in order to generate a token for backend usage has proven to be challenging. Integrating this functionality with Angular and TypeScript requires careful coordination. Currently, the angular-stripe and stripe. ...

What categories do input events fall into within Vue?

What Typescript types should be used for input events in Vue to avoid missing target value, key, or files properties when using Event? For example: <input @input="(e: MISSING_TYPE) => {}" /> <input @keypress="(e: MISSING_TYPE) = ...

The ID data from my axios.delete request is not properly transferring to the req.body, causing issues with deleting from mySQL

Currently, I am utilizing Axios to manage the requests in my application. Upon testing with PostMan, it has been confirmed that my DELETE request is functioning properly. However, when implementing the code for the front end, I encountered a 404 error duri ...

Having difficulty transitioning a function with a promise from JavaScript to TypeScript

I am currently in the process of transitioning my existing NodeJS JavaScript code to TypeScript for a NodeJS base, which will then be converted back to JavaScript when running on NodeJS. This approach helps me maintain clear types and utilize additional fe ...

The @Input directive is not compatible with the OnPush change detection strategy

page.html <app-parcel-delivery-cost-promo [parcelDeliveryCost]="parcelDeliveryCost"> </app-parcel-delivery-cost-promo> page.ts changeDetection: ChangeDetectionStrategy.OnPush, parcelDeliveryCost: Partial<ParcelDeliveryCostModel>; ...

How can I use the import statement to incorporate the 'posts.routes.js' file into my app using app?

Searching high and low for answers but coming up empty. When setting up an express app and including a file of routes, you typically encounter guidance on using the following statement: require('./app/routes/posts.routes.js')(app) As per nodejs. ...

Ways to eliminate text following a string substitution

When running the code below with keys assigned to summer, spring, fall, and winter, the output for ins would be: ['req.body.summer, req.body.spring, req.body.fall, req.body.winter'] I need to eliminate the surrounding string from the replace co ...

What is the process for combining two interface declarations into a single interface?

I have a question regarding organizing the properties of an interface: export interface IInvoicesData { invoice: IInvoice; invoiceWithTotals: IInvoice & IInvoiceTotals; } Currently, everything is functioning smoothly and I am able to consolid ...