amazon web services - Hvordan kan jeg bruke TypeScript Partials for å teste AWS Lambda?
Veldig likt Bruke delvis form for enhetstesting med TypeScript, men jeg klarer ikke å forstå hvorfor Partial-typen blir sett på som inkompatibel med fullversjonen.
Jeg har en enhetstest som sjekker om en lambda returnerer 400 hvis body i en AWS lambda-hendelse ikke er gyldig. For å unngå å lage støy for kollegaene mine, ønsker jeg ikke å lage invalidEvent med alle egenskapene til en full APIGatewayProxyEvent. Derfor bruker jeg en 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);
});
Linjen const { statusCode }=await handler(invalidEvent); feiler under kompilering med:
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)
Jeg forstår at APIGatewayProxyEvent body kan være string | null (fra å se på typene), men hvor kom string | null | undefined fra? Hvorfor er ikke min body - som er en streng - en gyldig body for APIGatewayProxyEvent
Hvordan kan jeg bruke TypeScript Partials for å teste AWS Lambda?
Jeg kunne brukt as for å gjøre typepåstander, men jeg synes Partials er mer eksplisitt. Følgende kode fungerer derimot:
const invalidEvent = { body: JSON.stringify({ foo: "bar" }) } as APIGatewayProxyEvent;
Oppdatering: bruke Omit og Pick for å lage en ny type
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);
});
Feiler med:
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)
Svar #1
Jeg klarer ikke å forstå hvorfor Partial-typen blir sett på som inkompatibel med fullversjonen
Fundamentalt er det uunngåelig - du begynte med noe som krevde at body-egenskapen måtte være string | null, og laget noe med et svakere krav string | null | undefined. Du tilbød body i dette tilfellet, men det spiller ingen rolle fordi handler bare ser invalidEvent gjennom Partial<APIGatewayProxyEvent>-grensesnittet, og kompilatoren vet at egenskapen kan mangle. Som du har sett, hvis du fikser den ene egenskapen for å gjøre den påkrevd igjen, klager den bare på neste i stedet.
I tilfeller hvor du ikke eier handler-API-en, har du egentlig bare tre valg, ingen av dem er ideelle:
- Faktisk gi en full
APIGatewayProxyEvent(se slutten for en snarvei til dette); - Henvend deg til kompilatoren og påstå at testobjektet ditt er et fullstendig
APIGatewayProxyEventmed en typepåstand; eller - Fortell kompilatoren om ikke å sjekke det i det hele tatt med en
// @ts-ignore-kommentar.
Å bruke Partial er vanligvis bare et steg i alternativ 2, ved å bruke:
const thing: Partial<Thing> = { ... };
whatever(thing as Thing);
i stedet for:
const thing = { ... } as Thing;
whatever(thing);
Hvis du eier handler's API, er den beste måten å gjøre dette på å anvende grensesnittsegregeringsprinsippet og være spesifikk om hva det faktisk trenger for å gjøre jobben sin. Hvis det for eksempel bare er body:
type HandlerEvent = Pick<APIGatewayProxyEvent, "body">;
function handler(event: HandlerEvent) { ... }
Et komplett APIGatewayProxyEvent er fortsatt et gyldig argument for handler, fordi det definitivt har en body (og det faktum at det også har andre egenskaper er irrelevant, de er utilgjengelige via HandlerEvent). Dette fungerer også som innebygd dokumentasjon på hva du faktisk bruker fra det komplette objektet.
I testene dine kan du nå bare opprette det mindre objektet:
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);
});
Som en bonus, hvis det viser seg senere at du trenger å få tilgang til flere av hendelsens egenskaper inne i handler, kan du oppdatere typen:
type HandlerEvent = Pick<APIGatewayProxyEvent, "body" | "headers">;
og du vil få feil overalt, du må oppdatere testdataene for å ta hensyn til det. Dette ville ikke skje med const invalidEvent={ ... } as APIGatewayProxyEvent;, du må spore endringene ved å se hvilke tester som feilet under kjøring.
En annen snarvei jeg har sett brukt med alternativ 1 er å pakke en funksjon rundt partialen, og gi fornuftige standardverdier:
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);
});
I dette tilfellet bør du gjøre standardverdiene så minimale som mulig (null, 0, "", tomme objekter og arrays, ...), for å unngå at noen bestemt oppførsel avhenger av dem.
Svar #2
Problemet her er at den delvise typen gjør alle objektets egenskaper valgfrie:
type MyObj = { myKey: string | null };
type MyPartialObj = Partial<MyObj>;
// MyPartialObj is: { myKey?: string | null | undefined }
I typen MyObj var typen til myKey string | null. Når vi konverterte den til MyPartialObj ble typen til myKey valgfri og kan dermed være undefined. Så nå er typen string | null | undefined
Din APIGatewayProxyEvent-type forventer at body skal være string | null, men siden du har gjort den delvis, sier du at body kan også være undefined. Ja, du har definert den, men du har aldri gjort en typeinnsnevring for å validere at det faktisk er en streng. Så alt TypeScript kan basere seg på er typen du har tildelt, som igjen er Partial.
OPPDATERING: For å bygge videre på det @jonrsharpe har sagt i kommentarene. Min forrige løsning ser ut til ikke å fungere heller, den bare flyttet feilen til neste egenskap i APIGatewayProxyEvent. Se deres svar. Problemet er at du prøver å mocke en del av dataene, og typen forventer at alle dataene er til stede. Det kan være enklest å lage et objekt som har de minimale verdiene for hver egenskap i stedet for å prøve å forfalske det. Du kan bruke as, men det undergraver hele poenget med å bruke typesystemet i første omgang.
Tidligere svar: En løsning kan være å gjøre alle verdier valgfrie bortsett fra body:
const invalidEvent: Omit<Partial<APIGatewayProxyEvent>, 'body'> & Pick<APIGatewayProxyEvent, 'body'> = {
body: JSON.stringify({ foo: "bar" }),
};
Unngå også å bruke as x som pesten, med mindre du er helt sikker på hva du gjør.
