Can TypeScript Partials be utilized for testing AWS Lambda functions?

Similar to a discussion on unit testing with typescript, I am puzzled by the incompatibility of the Partial type with the full version.

In my unit test scenario, I need to check if a lambda returns 400 when the body in an AWS lambda event is not valid. To keep things simple and clear for my colleagues, I want to avoid creating unnecessary noise by using a Partial<APIGatewayProxyEvent> instead of creating a complete invalidEvent with all properties of 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);
  });

However, the line

const { statusCode } = await handler(invalidEvent);
fails compilation due to:

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)

I understand that the body of APIGatewayProxyEvent can be string or null, but why does it show as string or null or undefined? Why is my string not considered a valid body for APIGatewayProxyEvent?

How can TypeScript Partials be effectively used to test AWS Lambda functions?

The usage of as for type assertions could be an alternative approach, but I prefer the clarity provided by Partials. Although, the following code also works:

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

Update: utilizing Omit and Pick to define a new 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);
});

This attempt results in:

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)

Answer №1

I am struggling to comprehend why the Partial type is considered incompatible with the full version

Essentially, this is unavoidable - you initially had a requirement for the body property to be string | null, and then transitioned to a weaker requirement of string | null | undefined. In this scenario, even though you did provide the body, it does not matter because the handler only interacts with invalidEvent through the

Partial<APIGatewayProxyEvent>
interface, and the compiler recognizes that the property could potentially be missing. As demonstrated, if you make one property required again, the compiler will simply flag the next one as an issue instead.

In situations where you do not have ownership over the handler API, you are left with three less-than-ideal options:

  1. Actually supply a complete APIGatewayProxyEvent (refer to the end for a shortcut);
  2. Assert to the compiler that your test object is indeed a full APIGatewayProxyEvent using a type assertion; or
  3. Instruct the compiler to ignore it entirely with a // @ts-ignore comment.

The use of Partial typically falls under option 2, utilizing:

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

instead of:

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

If you manage the handler's API, the most effective approach is to adhere to the interface segregation principle and specify the exact requirements for its functionality. For instance, if it solely requires the body:

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

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

A complete APIGatewayProxyEvent remains a valid argument for handler, since it undeniably contains a body (with the other properties being immaterial within the context of HandlerEvent). This also serves as embedded documentation depicting the specific data being consumed from the comprehensive object.

In your tests, you can now effortlessly create the abbreviated object:

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);
});

Additionally, should it become necessary in the future to access more properties of the event within handler, adjusting the type allows for easy identification of areas requiring updated test data. This proactive measure eliminates runtime failures due to overlooked modifications, a potential pitfall with

const invalidEvent = { ... } as APIGatewayProxyEvent;
.


An alternative shortcut I've noted with option 1 involves encapsulating a function around the partial object, providing default values:

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);
});

In this scenario, aim to keep the defaults as minimal as feasible (null, 0, "", empty objects and arrays, etc.) to prevent any specific behavior dependency on them.

Answer №2

This issue arises because the partial type converts all object properties to optional:

type MyObject = { key: string | null };

type PartialObj = Partial<MyObject>;
// PartialObj becomes: { key?: string | null | undefined }

In the definition of MyObject, the key was of type string | null. After conversion to PartialObj, the key type is now optional, allowing it to be undefined. Hence, its new type is string | null | undefined.

The APIGatewayProxyEvent type expects the body to be of type string | null. However, by making it partial, you are implying that body could also potentially be undefined. Although you have set a type, without proper type narrowing, TypeScript only knows that it is partial.

UPDATE: Building upon @jonrsharpe's comment, my initial solution seems ineffective as it simply shifts the error to the next property in APIGatewayProxyEvent. Refer to their response for more insights. The crux of the issue lies in attempting to mock a part of the data while the type necessitates all data to be present. It might be simpler to create an object with minimal values for each property rather than trying to fake it. Using as may provide a workaround, but goes against the purpose of utilizing the type system.

A potential approach is to make all values optional except for body:

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

Avoid resorting to as x unless absolutely necessary or fully understood.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Looking to retrieve the AssetLoadedFunc properties in the LoadAssets function? Wondering if you should use TypeScript or JavaScript

When I invoke this.AssetLoadedFunc within the function LoadAssets(callback, user_data) LoadAssets(callback, user_data) { this.glg.LoadWidgetFromURL("assets/Js/scrollbar_h.g", null, this.AssetLoaded, { name: "scrollb ...

Encountering a Vueify Typescript error in a Vue project

Recently diving into the world of Vue, I was able to successfully create a sample app using gulp, vueify, and TypeScript. To showcase what's happening and shed light on an issue I'm facing, here are snippets of the key code segments: Menu.ts im ...

Is there a way to determine if a string within an array includes any special characters?

I am trying to determine if any string in an array contains special characters. I have experimented with various methods like RegExp, test, includes, and contains, but none of them give me the desired outcome. Instead, they all return false for every str ...

Error reported: "require" identifier not found in Typescript. Issue identified in IONIC 3 development environment

Error Encountered in Typescript: Cannot Find the Name 'require' Location: C:/Users/me/project/src/pages/home/home.ts // Need to require the Twilio module and create a REST client const client = require('twilio')(accountSid, ...

"In Nextjs, it is not possible to utilize the alert function or assign variables directly to the window object

Are you attempting to quickly sanity test your application using the window object's alert function, only to encounter errors like "Error: alert is not defined"? import Image from "next/image"; alert('bruh'); export default function Home() ...

TS object encountering a restriction with an inaccessible method

I'm facing a challenge trying to utilize a method stored on a Typescript class within a Vue component. When I attempt to use a method defined on that class from another class (which also happens to be a Typescript Vue component), the console throws a ...

Distinguish the components of a standard union in TypeScript

I am looking to incorporate the following feature: const foo = <S>(newState: S | ((prevState: S) => S)) => {...} This function should accept either a new state of type S, or a function that generates the new state from the old state. In my i ...

What is the proper way to access and modify the child component within a parent component?

I am working with nested components as shown below: app.parent.component > app.container.component > app.containeritem.component Here is an example: app.parent.component import ... @Component({ selector:'parent', template: &apos ...

Issue with updating the div to show the user's submission form in Angular is causing difficulties

After a user submits a form, I would like to display their submission by hiding the form area and showing the response in that same area. Upon submitting the form, my goal is to show a mat-spinner until a response is received from the server. The compone ...

What is the best way to assign or convert an object of one type to another specific type?

So here's the scenario: I have an object of type 'any' and I want to assign it an object of type 'myResponse' as shown below. public obj: any; public set Result() { obj = myResponse; } Now, in another function ...

Issue encountered when using await in Tensorflow.js sample code with TypeScript

After going through the official tensorflow.js documentation, I attempted to test this example using typescript with tensorflow.js While trying to execute the code snippet provided in the tensorflow.js documentation, I encountered an error related to the ...

Establishing a connection between an RDS Database and PHP

Just getting started with Amazon Web Services and I'm running into an issue connecting to RDS. The connection is giving me an error message. $db_hostname="RDSEndpointwithport"; $db_username="username"; $db_password="password"; $db_name="databasename" ...

Removing Objects using Django's Delete Function

I'm facing an issue with deleting individual objects from my database. I have a code that retrieves a list of RDS hostnames from AWS and then compares those with the ones stored in my database. If a hostname is present in my DB but not returned by AWS ...

When using NextJS <Link, mobile users may need to tap twice to navigate

Whenever I use the NextJS <Link tag on my mobile device, I notice that I have to double-tap for the link to actually route to the desired page. Take a look at the code snippet below: <Link href="/methodology" passHref={true} ...

Angular is patiently awaiting the completion of the subscription

Currently, I am in the process of developing a test application using Angular. The challenge arises when I attempt to retrieve data through a Get request and then return a value based on that data. The code snippet below outlines the scenario: public getN ...

Can you explain the significance behind the syntax used in ngrx adapter selectors?

Stumbled upon this ngrx adapter example: export const { selectAll: selectAllItems } = adapter.getSelectors<State>(state => state.items); Confused about the assignment in this code snippet. Specifically, the notation involving a type: const ...

Struggling to retrieve dataset from Bootstrap 5 form while using Angular

With the combination of Angular and Bootstrap 5, I have crafted this unique HTML form: <div class="mb-3"> <label for="genreName"> Type name</label> <div *ngIf="!enterTheGenre" class="form-group&qu ...

Defining TypeScript type annotations: the art of declaring class constructors

I've developed my own JavaScript library (consisting of a single js file) with multiple classes. To incorporate it into my TypeScript projects, I created a d.ts file containing type definitions for each class. An example of the definition would be: ex ...

Steps for verifying the presence of an element in Selenium using JavaScript

I'm facing an issue with a Jest and Selenium test. I'm trying to verify if an element is removed from the DOM after being clicked. Here's what I've attempted: test('User removes button after clicking on it', async (done) =& ...

Tips for creating a person card like the one seen in MS Teams' WhoBot

Is there a way to create a person card with status and icons that resembles the fluent UI react persona component in MS adaptive cards for a bot framework project? I am looking to achieve something like the whobot person card shown below, including all att ...