如何使用 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 });
}
...
好吧,我正在尝试了解如何在我的 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 });
}
...