Jest.js 如何使用TypeScript Partials测试AWS Lambda?

lskq00tm  于 11个月前  发布在  Jest
关注(0)|答案(2)|浏览(105)

非常类似于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的body可以是string | null(从类型上看),但是string | null | undefined是从哪里来的?为什么我的body-这是一个字符串-不是APIGatewayProxyEvent的有效body

如何使用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)

eqqqjvef

eqqqjvef1#

我不明白为什么Partial类型被认为与完整版本不兼容
从根本上说,这是不可避免的-您从要求body属性为string | null的东西开始,并创建了具有较弱要求string | null | undefined的东西。在这种情况下,您确实提供了body,但这并不重要,因为handler只通过Partial<APIGatewayProxyEvent>接口看到invalidEvent,编译器知道属性 could 正如您所看到的,如果您修补了一个属性,使其再次被要求,它只会抱怨下一个属性。
如果您不拥有handler API,您实际上只有三种选择,其中没有一种是理想的:
1.实际上提供了一个完整的APIGatewayProxyEvent(见结尾的快捷方式);
1.向编译器声明您的测试对象 * 是 * 一个带有类型Assert的完整APIGatewayProxyEvent;或者
1.告诉编译器不要用// @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的另一个快捷方式是在partial周围 Package 一个函数,提供合理的默认值:

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"",空对象和数组,...),以避免依赖于它们的任何特定行为。

xoefb8l8

xoefb8l82#

这里的问题是partial类型将所有对象属性转换为optional:

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,然而,因为你已经使它成为partial,你说body * 可能 * 也是undefined。是的,你已经定义了它,但你从来没有做过type narrowing来验证它确实是一个字符串。所以所有的TypeScript必须离开的是你分配的类型,这也是Partial。

**更新:**扩展@jonrsharpe在评论中所说的内容。我以前的解决方案似乎不起作用,它只是将错误推到APIGatewayProxyEvent中的下一个属性上。请参阅他们的答案。问题是你试图模拟一部分数据,而类型期望所有数据都存在。可能最简单的方法是为每个属性创建一个具有最小值的对象,而不是试图伪造它。你可以 * 使用as,但这首先违背了使用类型系统的全部意义。
**上一个答案:**一个解决方案可能是让所有值都是可选的,除了body

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


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

相关问题