如何使用 Jest 对此控制器进行正确的单元测试

How to make a correct unit test for this controller with Jest

好吧,我正在尝试了解如何在我的 Express 应用程序上对我的控制器进行单元测试,但我还没有找到如何这样做...这些是我现在的代码文件:

// 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 });
            
            // if(user instanceof Error) {
            //     return response.send({ error: user.message });
            // }

            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 };

而我要测试的是CreateUserController,但我认为它是以我无法测试的方式编写的,或者我可以吗? 现在,我有这样的东西:

// 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: 'email@d.com',
            password: '123456',
            admin: true
        };
        const returnUserData = {
            id: userId,
            name: 'Fulano',
            email: 'email@d.com',
            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);
    });
});

是的,我知道有错误但不会运行,但目前我只知道这些

控制器的单元测试意味着另一个控制器的依赖项运行良好。在您的情况下,控制器只有一个依赖项 - CreateUserService.

为了测试控制器,你可以想象服务returns一个用户或抛出一个意想不到的异常。并且您希望 .send 函数将使用正确的参数调用。

需要说明的东西太多了(我是这么认为的),你可以自己去调查一下。

// 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', () => {
  const name = 'name';
  const email = 'email';
  const admin = true;
  const password = 'password';

  let controller: CreateUserController;
  let service: jest.Mocked<CreateUserService>;

  let request: Request;
  let response: Response;

  beforeEach(() => {
    request = {
      body: { name, email, admin, password },
    } as Request;
    response = {
      send: jest.fn().mockReturnThis(),
      status: jest.fn().mockReturnThis(),
    } as any;

    service = {
      execute: jest.fn(),
    }; // an instance of the service
    mocked(CreateUserService).mockImplementation(() => {
      return service;
    }); // mock the service constructor to return our mocked instance

    controller = new CreateUserController();
  });

  it('should response with user object when service returns the user', async () => {
    const user: any = 'user mocked';
    service.execute.mockResolvedValue(user);

    await controller.handle(request, response);

    expect(response.send).toHaveBeenCalledWith(user);
    expect(service.execute).toHaveBeenLastCalledWith({ name, email, admin, password });
  });

  it('should response with status 500 and error object when service throws a error', async () => {
    const error = new Error('Timed out!');
    service.execute.mockRejectedValue(error);

    await controller.handle(request, response);

    expect(response.status).toHaveBeenCalledWith(500);
    expect(response.send).toHaveBeenCalledWith({ error });
    expect(service.execute).toHaveBeenLastCalledWith({ name, email, admin, password });
  });
});

第二种情况会失败,因为我希望控制器必须以状态 500 响应,要使其通过,请更新您的控制器:

...
    } catch (error) {
      return response.status(500).send({ error });
    }
...