如何使用 TypeScript Partials 测试 AWS Lambda?
How can I use TypeScript Partials to test AWS Lambda?
与 非常相似,但我无法理解为什么部分类型被视为与完整版本不兼容。
我有一个单元测试,如果 AWS lambda 事件中的 body
无效,它会检查 lambda returns 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
的有效正文
如何使用 TypeScript Partials 测试 AWS Lambda?
我可以使用 但我发现 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)
这里的问题是部分类型将所有对象属性转换为可选:
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
类型期望 body
为 string | null
,但是由于您将其设为部分类型,所以您说的是 body
可以也就是undefined
。是的,您已经定义了它,但是您从未执行 type narrowing 来验证它确实是一个字符串。因此,所有 TypeScript 必须离开的是您分配的类型,这又是部分的。
更新:
扩展@jonrsharpe 在评论中所说的内容。我以前的解决方案似乎对以太不起作用,它只是将错误推到 APIGatewayProxyEvent
中的下一个 属性。 查看他们的回答。问题是您正在尝试模拟一部分数据,并且该类型期望所有数据都存在。制作一个每个 属性 具有最小值的对象而不是试图伪造它可能是最简单的。您 可以 使用 as
但这首先违背了使用类型系统的全部意义。
上一个答案:
一个解决方案可能是使所有值都是可选的,除了 body
:
const invalidEvent: Omit<Partial<APIGatewayProxyEvent>, 'body'> & Pick<APIGatewayProxyEvent, 'body'> = {
body: JSON.stringify({ foo: "bar" }),
};
此外,避免像瘟疫一样使用 as x
,除非您完全确定自己在做什么。
I'm failing to understand why the Partial type is seen as being incompatible with the full version
从根本上说,这是不可避免的 - 您从要求 body
属性 为 string | null
的内容开始,然后创建了 string | null | undefined
要求较弱的内容。在这种情况下,您 确实 提供了 body
,但这并不重要,因为 handler
只能通过 Partial<APIGatewayProxyEvent>
看到 invalidEvent
接口和编译器知道 属性 可能 丢失。如您所见,如果您修补那个 属性 再次被要求,它只会抱怨下一个。
在您不拥有 handler
API 的情况下,您实际上只有三个选择,none 其中最理想的是:
- 实际提供一个完整的
APIGatewayProxyEvent
(见文末的快捷方式);
- 向编译器声明你的测试对象是一个完整的
APIGatewayProxyEvent
和type assertion;或
- 用
// @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);
});
在这种情况下,您应该使默认值尽可能小(null
、0
、""
、空对象和数组,...),以避免任何特定行为取决于他们。
与
我有一个单元测试,如果 AWS lambda 事件中的 body
无效,它会检查 lambda returns 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
如何使用 TypeScript Partials 测试 AWS Lambda?
我可以使用
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)
这里的问题是部分类型将所有对象属性转换为可选:
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
类型期望 body
为 string | null
,但是由于您将其设为部分类型,所以您说的是 body
可以也就是undefined
。是的,您已经定义了它,但是您从未执行 type narrowing 来验证它确实是一个字符串。因此,所有 TypeScript 必须离开的是您分配的类型,这又是部分的。
更新:
扩展@jonrsharpe 在评论中所说的内容。我以前的解决方案似乎对以太不起作用,它只是将错误推到 APIGatewayProxyEvent
中的下一个 属性。 查看他们的回答。问题是您正在尝试模拟一部分数据,并且该类型期望所有数据都存在。制作一个每个 属性 具有最小值的对象而不是试图伪造它可能是最简单的。您 可以 使用 as
但这首先违背了使用类型系统的全部意义。
上一个答案:
一个解决方案可能是使所有值都是可选的,除了 body
:
const invalidEvent: Omit<Partial<APIGatewayProxyEvent>, 'body'> & Pick<APIGatewayProxyEvent, 'body'> = {
body: JSON.stringify({ foo: "bar" }),
};
此外,避免像瘟疫一样使用 as x
,除非您完全确定自己在做什么。
I'm failing to understand why the Partial type is seen as being incompatible with the full version
从根本上说,这是不可避免的 - 您从要求 body
属性 为 string | null
的内容开始,然后创建了 string | null | undefined
要求较弱的内容。在这种情况下,您 确实 提供了 body
,但这并不重要,因为 handler
只能通过 Partial<APIGatewayProxyEvent>
看到 invalidEvent
接口和编译器知道 属性 可能 丢失。如您所见,如果您修补那个 属性 再次被要求,它只会抱怨下一个。
在您不拥有 handler
API 的情况下,您实际上只有三个选择,none 其中最理想的是:
- 实际提供一个完整的
APIGatewayProxyEvent
(见文末的快捷方式); - 向编译器声明你的测试对象是一个完整的
APIGatewayProxyEvent
和type assertion;或 - 用
// @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);
});
在这种情况下,您应该使默认值尽可能小(null
、0
、""
、空对象和数组,...),以避免任何特定行为取决于他们。