【问题标题】:Express/Typescript testing with Jest/Supertest使用 Jest/Supertest 进行 Express/Typescript 测试
【发布时间】:2021-08-15 17:48:29
【问题描述】:

我目前正在尝试测试一个快速 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

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

谢谢!

【问题讨论】:

    标签: typescript express jestjs supertest


    【解决方案1】:

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

    你在单元测试中使用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();
    }
    

    单独关注

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

    1. 路由(映射路由处理程序)
    2. 控制器(您的路由处理程序)
    3. 服务(与存储库/模型/实体交互)
    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({});
      }
    }
    

    测试

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

    了解详情

    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

    【讨论】:

    • 哇,这是一个了不起的答案!这种架构真的让我想起了 NestJs。我现在可以看到我需要对我的代码架构进行更多思考,因为 Express 可能会有点疯狂。我什至不知道 Express 可以在没有 Supertest 的情况下进行测试。
    • @mesamess Nest.js 提供其架构是有原因的!在切换之前,我在 Express 上花了一段时间,并将其视为围绕它的固执己见的包装器,以制作更强大/可测试的软件。如果您是新手,Express 可能是更好的选择,因为它可以灵活地创建对您有意义的架构,并在测试/部署期间查看它是如何工作的。万事如意!
    • 可以再问一个问题吗?我正在尝试实现 ProductService 类,但是在构造函数中,this.model = model 表示 ProductService 类型上不存在模型,并且构造函数的参数(模型:ProductModel)表示它指的是值而不是类型.所以我想和你核实一下 Product Model 是否应该是一个接口或其他东西,因为目前它是 Mongoose 的一个模式。
    • @mesamess 那是我的错,我最初是在 JS 中回答的,并没有在 TS 中更新它(现在更新)。要解决您的最后一个问题:tomanagle.medium.com/… 应该会有所帮助!但你是对的,它需要一个接口。 (我也将此链接添加到我的答案中)。
    猜你喜欢
    • 2021-11-28
    • 2021-01-13
    • 1970-01-01
    • 2020-09-14
    • 2019-07-31
    • 1970-01-01
    • 2023-04-01
    • 2017-02-25
    • 1970-01-01
    相关资源
    最近更新 更多