首页 > 解决方案 > 如何使用 TypeScript Partials 测试 AWS Lambda?

问题描述

非常类似于Using partial shape for unit testing with typescript但我不明白为什么 Partial 类型被视为与完整版本不兼容。

我有一个单元测试,如果bodyAWS lambda 事件无效,则检查 lambda 是否返回 400。为了避免给我的同事制造噪音,我不想invalidEvent使用 full 的所有属性进行创建APIGatewayProxyEvent。因此使用Partial<APIGatewayProxyEvent>.

  it("should return 400 when request event is invalid", async () => {
    const invalidEvent: Partial<APIGatewayProxyEvent> = {
      body: JSON.stringify({ foo: "bar" }),
    };
    const { statusCode } = await handler(invalidEvent);
    expect(statusCode).toBe(400);
  });

const { statusCode } = await handler(invalidEvent);行编译失败:

Argument of type 'Partial<APIGatewayProxyEvent>' is not assignable to parameter of type 'APIGatewayProxyEvent'.
  Types of property 'body' are incompatible.
    Type 'string | null | undefined' is not assignable to type 'string | null'.
      Type 'undefined' is not assignable to type 'string | null'.ts(2345)

我知道APIGatewayProxyEvent身体可以string | null(从查看类型)但是string | null | undefined从哪里来?为什么我的body- 这是一个字符串 - 不是一个有效的身体APIGatewayProxyEvent

如何使用 TypeScript Partials 测试 AWS Lambda?

我可以as用来做类型断言,但我发现 Partials 更明确。以下代码虽然有效:

    const invalidEvent = { body: JSON.stringify({ foo: "bar" }) } as APIGatewayProxyEvent;

更新:使用 Omit 和 Pick 创建一个新类型

  type TestingEventWithBody = Omit<Partial<APIGatewayProxyEvent>, "body"> & Pick<APIGatewayProxyEvent, "body">;

  it("should return 400 when request event is invalid", async () => {
    const invalidEvent: TestingEventWithBody = { body: JSON.stringify({ foo: "bar" }) };
    const { statusCode } = await handler(invalidEvent);
    expect(statusCode).toBe(400);
  });

失败:

Argument of type 'TestingEventWithBody' is not assignable to parameter of type 'APIGatewayProxyEvent'.
  Types of property 'headers' are incompatible.
    Type 'APIGatewayProxyEventHeaders | undefined' is not assignable to type 'APIGatewayProxyEventHeaders'.
      Type 'undefined' is not assignable to type 'APIGatewayProxyEventHeaders'.ts(2345)

标签: typescriptamazon-web-servicesjestjspartialsts-jest

解决方案


我不明白为什么 Partial 类型被视为与完整版本不兼容

从根本上说,这是不可避免的——你从需要body属性的东西开始string | null,然后创建了要求更弱的东西string | null | undefined。在这种情况下,您确实提供了body,但这并不重要,因为handlerinvalidEvent通过Partial<APIGatewayProxyEvent>接口查看,编译器知道该属性可能丢失。如您所见,如果您修补该属性以再次需要它,它只会抱怨下一个属性。

在您不拥有handlerAPI 的情况下,您实际上只有三个选择,其中没有一个是理想的:

  1. 实际上提供了一个完整的APIGatewayProxyEvent(见最后的快捷方式);
  2. 向编译器声明您的测试对象APIGatewayProxyEvent带有类型断言的完整对象;或者
  3. // @ts-ignore用注释告诉编译器根本不要检查它。

使用Partial通常只是选项 2 中的一个步骤,使用:

const thing: Partial<Thing> = { ... };
whatever(thing as Thing);

代替:

const thing = { ... } as Thing;
whatever(thing);

如果您拥有 handler的 API,那么最好的方法是应用接口隔离原则,并具体说明它实际需要什么来完成其工作。如果只是body,例如:

type HandlerEvent = Pick<APIGatewayProxyEvent, "body">;

function handler(event: HandlerEvent) { ... } 

fullAPIGatewayProxyEvent仍然是 的有效参数handler,因为它确实具有 a body(并且它也具有其他属性的事实是无关紧要的,它们无法通过 访问HandlerEvent)。这也可以作为关于您从完整对象中实际使用的内容的内置文档。

在您的测试中,您现在可以创建较小的对象:

it("should return 400 when request event is invalid", async () => {
  const invalidEvent: HandlerEvent = { body: JSON.stringify({ foo: "bar" }) };
  const { statusCode } = await handler(invalidEvent);
  expect(statusCode).toBe(400);
});

作为奖励,如果后来发现您需要访问更多事件的内部属性handler,您可以更新类型:

type HandlerEvent = Pick<APIGatewayProxyEvent, "body" | "headers">;

并且您会在需要更新测试数据以考虑到这一点的任何地方遇到错误。不会发生这种情况const invalidEvent = { ... } as APIGatewayProxyEvent;,您必须通过查看哪些测试在运行时失败来跟踪更改。


我见过的与选项 1 一起使用的另一个快捷方式是在部分周围包装一个函数,提供合理的默认值:

function createTestData(overrides: Partial<APIGatewayProxyEvent>): APIGatewayProxyEvent {
  return {
    body: null,
    headers: {},
    // etc.
    ...overrides,
  };
}

it("should return 400 when request event is invalid", async () => {
  const invalidEvent = createTestData({ body: JSON.stringify({ foo: "bar" }) });
  const { statusCode } = await handler(invalidEvent);
  expect(statusCode).toBe(400);
});

在这种情况下,您应该使默认值尽可能小(null, 0, "", 空对象和数组,...),以避免依赖于它们的任何特定行为。


推荐阅读