Express/Typescript 使用 Jest/Supertest 进行测试

Express/Typescript testing with Jest/Supertest

我目前正在尝试测试 express 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

谁能告诉我这是什么问题?

谢谢!

Could anyone advise what the problem is here?

您在可以避免的单元测试中使用 supertestsupertest 也接受了您的 express 应用程序的实例,并且似乎提供了 products?还是 products 您的快递实例?您可能会发现的另一个问题是 ProductModel.find 直到 测试调用之后才被模拟,因为您正在使用全局实例。

在测试时,通过在设计代码时考虑到清晰的抽象和测试,我们可以让我们的生活变得更轻松。

依赖项

当您设计代码时,将代码设计为接受依赖实例 arguments/properties:


// 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);
  }
}
</pre>

这使我们的测试更容易,因为我们可以自信地说正确的实例(真实或模拟)已提供给控制器、服务、存储库等。

这也避免了使用像这样的东西:


// ... some bootstrap function
if (process.env.NODE_ENV === 'test') {
  someInstance = getMockInstance()
} else {
  someInstance = RealInstance();
}
</pre>

单独关注

当您处理请求时,需要做一些事情:

  1. 路由(映射路由处理程序)
  2. 控制器(你的路由处理器)
  3. 服务(与 Repositories/Models/Entities 交互)
  4. 模型(您的 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({});
  }
}
</pre>

测试

我们现在剩下几个可以轻松测试的组件,以确保正确的输入产生正确的输出。在我看来,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')
  });
})
</pre>

对于您的应用程序,您需要确定将依赖项注入正确 类 的合适方法。您可能会决定接受所需模型的 main 函数适合您,或者可能会认为 https://www.npmjs.com/package/injection-js 等更强大的功能会起作用。

避免 OOP

如果您希望避免使用对象,请接受实例作为函数参数:productServiceGetAll(params: SomeParams, model?: ProductModel).

了解更多

  1. https://www.guru99.com/unit-testing-guide.html
  2. https://jestjs.io/docs/mock-functions
  3. https://levelup.gitconnected.com/typescript-object-oriented-concepts-in-a-nutshell-cb2fdeeffe6e?gi=81697f76e257
  4. https://www.npmjs.com/package/supertest
  5. https://tomanagle.medium.com/strongly-typed-models-with-mongoose-and-typescript-7bc2f7197722