【问题标题】:How can I use TypeScript Partials to test AWS Lambda?如何使用 TypeScript Partials 测试 AWS Lambda?
【发布时间】:2021-10-29 23:19:49
【问题描述】:

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

我有一个单元测试,如果 AWS lambda 事件中的 body 无效,它会检查 lambda 是否返回 400。为了避免给我的同事制造噪音,我不想创建具有完整APIGatewayProxyEvent 的所有属性的invalidEvent。因此使用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 to do type assertions,但我发现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)

【问题讨论】:

  • string | null | undefined 来自于获取string | null 并添加undefined,这就是Partial 所做的,以使每个属性都是可选的。正文是一个字符串,但这无关紧要——它是通过一个可能不是的接口访问的。
  • @jonrsharpe 当然可以,但是为什么使用字符串作为键,而不允许使用字符串呢?
  • 并且该错误不是来自分配给invalidEvent,当您尝试将它传递给handler 时它会出现,它需要APIGatewayProxyEvent 而不是@ 987654346@(因为后者可能缺少handler需要的任何或所有属性)。
  • 当您调用函数时,您使用字符串 - 这就是问题所在。这正是错误消息告诉您的内容。您正在使用Partial&lt;APIGatewayProxyEvent&gt;,其bodystring | null | undefined“我无法理解为什么 Partial 类型被视为与完整版本不兼容” - 因为根据定义(假设您没有在所有道具已经是可选的东西上冗余使用它) 这就是 Partial 所做的,它使需要的属性不再需要。
  • 是的,这将是接口隔离原则的应用,将handler 限制在它实际工作所需的属性上。 APIGatewayProxyEventPartial&lt;APIGatewayProxyEvent&gt; 或通过 PickOmit 具有子集或属性的东西兼容,反之则不然。

标签: typescript amazon-web-services jestjs partials ts-jest


【解决方案1】:

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

从根本上说,这是不可避免的 - 您从要求 body 属性为 string | null 的东西开始,并创建了具有较弱要求 string | null | undefined 的东西。在这种情况下,您确实提供了body,但这并不重要,因为handler 仅通过Partial&lt;APIGatewayProxyEvent&gt; 接口看到invalidEvent,并且编译器知道该属性可以 不见了。如您所见,如果您修补该属性以再次需要它,它只会抱怨下一个属性。

如果您没有handler API,您实际上只有三个选择,没有一个是理想的:

  1. 实际上提供了一个完整的APIGatewayProxyEvent(见末尾的快捷方式);
  2. 向编译器声明您的测试对象完整的APIGatewayProxyEventtype assertion;或
  3. // @ts-ignore 注释告诉编译器根本不要检查它。

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

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

代替:

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

如果您拥有 handler 的 API,最好的方法是应用 interface segregation principle 并具体说明它实际需要做的工作。如果只是body,例如:

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

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

一个完整的APIGatewayProxyEvent 仍然是handler 的有效参数,因为它肯定有一个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);
});

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

【讨论】:

    【解决方案2】:

    这里的问题是分部类型将所有对象属性转换为可选:

    type MyObj = { myKey: string | null };
    
    type MyPartialObj = Partial<MyObj>;
    // MyPartialObj is: { myKey?: string | null | undefined }
    

    MyObj 类型中,myKey 类型为string | null。当我们将其转换为MyPartialObj 时,myKey 类型变为可选,因此有可能成为undefined。所以现在它的类型是string | null | undefined

    您的APIGatewayProxyEvent 类型期望bodystring | null,但是由于您已将其设为部分,您说body 可能 也可以是undefined。是的,您已经定义了它,但您从未使用type narrowing 来验证它确实是一个字符串。因此,TypeScript 所要做的就是你分配的类型,它又是 Partial。


    更新: 扩展 @jonrsharpe 在 cmets 中所说的内容。我之前的解决方案似乎无法正常工作,它只是将错误推送到APIGatewayProxyEvent 中的下一个属性。 查看他们的答案。问题是您正在尝试模拟数据的一部分,并且该类型期望所有数据都存在。为每个属性创建一个具有最小值的对象而不是试图伪造它可能是最简单的。您可以使用as,但这首先破坏了使用类型系统的全部意义。


    上一个答案: 一个解决方案可能是让所有值都是可选的,除了body

    const invalidEvent: Omit<Partial<APIGatewayProxyEvent>, 'body'> & Pick<APIGatewayProxyEvent, 'body'> = {
        body: JSON.stringify({ foo: "bar" }),
    };
    

    另外,避免像瘟疫一样使用as x,或者除非你完全确定自己在做什么。

    【讨论】:

    • 谢谢 Z!我理解您答案的第一部分(关于 Partial 使所有属性可选),但我无法理解“是的,您已经定义了它,但是您从未进行类型缩小来验证它确实是一个字符串。所以所有 TypeScript 都有to go off of 是您分配的类型,它又是 Partial。”。好的,所以 body 现在是 undefined 或 sting 或 null,为什么不指定一个 body(适合 'string')工作?
    • @mikemaccana 因为这不会改变invalidEvent 的类型。仅仅因为您确实为该属性分配了一个值,它不会改变您不是必需并且可能没有的事实。 handler 只知道它得到了 Partial&lt;APIGatewayProxyEvent&gt;
    • @mikemaccana,将body 作为string 包含在Partial&lt;APIGatewayProxyEvent&gt; 中是不是无效的,这就是为什么你对invalidEvent 的分配绝对是很好 - 它可以是 stringnull 或(由于 Partialundefined/完全缺失。
    • @mikemaccana 是的,我故意选择和省略相同的属性。 Omit 将其从 Partial 中省略。 Pick 是从原始的必需类型中挑选出来的。因此最终结果是body 是必需的,而所有其他都是可选的。
    • @mikemaccana 然后他们继续执行service.sendData(&lt;YourInterface&gt; data),这只是type assertions 的另一种语法,例如service.sendData(data as YourInterface)
    猜你喜欢
    • 1970-01-01
    • 2018-07-02
    • 2020-09-15
    • 2021-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多