问题描述
我目前正在尝试测试一个快速 API,我正在使用 Jest 和 Supertest,但是我似乎无法让它工作。
我的代码是:
router.get('/',async (req: Request,res: Response) => {
const products: ProductType[] = await ProductModel.find({});
res.send(products);
});
我的测试是:
describe('GET /',() => {
it('calls ProductModel.find and returns products',async () => {
const mockproducts = 'this is a product';
ProductModel.find = jest.fn().mockResolvedValueOnce(mockproducts);
const response = await request(products).get('/');
expect(response).toBe(mockproducts);
});
});
所以基本上,模拟的解析值一切正常,但是当我运行测试时,res.send 不起作用。
TypeError: res.send is not a function
谁能告诉我这里的问题是什么?
谢谢!
解决方法
谁能告诉我这里的问题是什么?
在可以避免的情况下,您在单元测试中使用了 supertest
。 supertest
还接受您的 express 应用程序实例,并且似乎提供了 products
?还是 products
是您的快速实例?您可能会发现的另一个问题是 ProductModel.find
不会被模拟,直到 after 在您使用全局实例时调用测试。
在测试时,我们可以通过在设计代码时考虑到清晰的抽象和测试,从而使我们的生活更轻松。
依赖项
在设计代码时,设计代码以接受依赖项实例作为参数/属性:
// as an argument
function makeHttpRequest(path,httpClient: AxoisInstance) {
return httpClient.get(path);
}
// as a property of object/class
class DependsOn {
constructor(private readonly httpClient: AxoisInstance) {}
request(path: string) {
return this.httpClient.get(path);
}
}
这使我们的测试更容易,因为我们可以自信地说正确的实例(真实或模拟)已提供给控制器、服务、存储库等。
这也避免使用以下内容:
// ... some bootstrap function
if (process.env.NODE_ENV === 'test') {
someInstance = getMockInstance()
} else {
someInstance = RealInstance();
}
单独的问题
当您处理请求时,需要做一些事情:
- 路由(映射路由处理程序)
- 控制器(您的路由处理程序)
- 服务(与存储库/模型/实体交互)
- 模型(您的
ProductModel
或数据层)
您目前拥有所有这些内联(我认为我们 99.99% 的人在选择 Express 时都会这样做)。
// product.routes.ts
router.get('/',ProductController.get); // pass initialised controller method
// product.controller.ts
class ProductController {
constructor(private readonly service: ProductService) {}
get(request: Request,response: Response) {
// do anything with request,response (if needed)
// if you need validation,try middleware
response.send(await this.service.getAllProducts());
}
}
// product.service.ts
class ProductService {
// Model IProduct (gets stripped on SO)
constructor(private readonly model: Model) {}
getAllProducts() {
return this.model.find({});
}
}
测试
我们现在剩下几个可以轻松测试的组件,以确保正确的输入产生正确的输出。在我看来,jest 是模拟方法、类和其他任何东西的最简单的工具之一,前提是您有良好的抽象允许您这样做。
// product.controller.test.ts
it('should call service.getAllProducts and return response',async () => {
const products = [];
const response = {
send: jest.fn().mockResolvedValue(products),};
const mockModel = {
find: jest.fn().mockResolvedValue(products),};
const service = new ProductService(mockModel);
const controller = new ProductController(service);
const undef = await controller.get({},response);
expect(undef).toBeUndefined();
expect(response.send).toHaveBeenCalled();
expect(response.send).toHaveBeenCalledWith(products);
expect(mockModel.find).toHaveBeenCalled();
expect(mockModel.find).toHaveBeenCalledWith();
});
// product.service.test.ts
it('should call model.find and return response',async () => {
const products = [];
const mockModel = {
find: jest.fn().mockResolvedValue(products),};
const service = new ProductService(mockModel);
const response = await service.getAllProducts();
expect(response).toStrictEqual(products);
expect(mockModel.find).toHaveBeenCalled();
expect(mockModel.find).toHaveBeenCalledWith();
});
// integration/e2e test (app.e2e-test.ts) - doesn't run with unit tests
// test everything together (mocking should be avoided here)
it('should return the correct response',() => {
return request(app).get('/').expect(200).expect(({body}) => {
expect(body).toStrictEqual('your list of products')
});
})
对于您的应用程序,您需要确定一种合适的方式将依赖项注入到正确的类中。您可以决定接受所需模型的 main
函数适合您,或者可以决定更强大的功能(如 https://www.npmjs.com/package/injection-js)。
避免面向对象
如果您希望避免使用对象,请接受实例作为函数参数:productServiceGetAll(params: SomeParams,model?: ProductModel)
。
了解详情
- https://www.guru99.com/unit-testing-guide.html
- https://jestjs.io/docs/mock-functions
- https://levelup.gitconnected.com/typescript-object-oriented-concepts-in-a-nutshell-cb2fdeeffe6e?gi=81697f76e257
- https://www.npmjs.com/package/supertest
- https://tomanagle.medium.com/strongly-typed-models-with-mongoose-and-typescript-7bc2f7197722