谁能告诉我这里的问题是什么?
你在单元测试中使用supertest,这是可以避免的。 supertest 也接受您的 express 应用程序的实例,并且似乎提供了 products?还是products 您的快递实例?您可能会发现的另一个问题是 ProductModel.find 直到 测试调用后才会模拟,因为您正在使用全局实例。
在测试时,我们可以通过在设计代码时考虑到清晰的抽象和测试,让我们的生活变得更加轻松。
依赖关系
当你设计你的代码时,设计代码接受依赖实例作为参数/属性:
// 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 这样更强大的函数可以工作。
避免 OOP
如果您希望避免使用对象,请接受实例作为函数参数: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