Skip to content

docs(parser): add section on how to test parser using decorator #2755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/utilities/parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,51 @@ The package `@types/aws-lambda` is a popular project that contains type definiti
Powertools parser utility also bring AWS Lambda event types based on the built-in schema definitions.

We recommend to use the types provided by the parser utility. If you encounter any issues or have any feedback, please [submit an issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new/choose).

## Testing your code

When testing your handler with [**parser decorator**](#parse-events) you need to use double assetion to bypass TypeScript type checking in your tests.
This is useful when you want to test the handler for invalid payloads or when you want to test the error handling.
If you are you use middy middleware, you don't need to do this.

=== "handlerDecorator.test.ts"

```typescript hl_lines="26"
--8<-- "examples/snippets/parser/unitTestDecorator.ts"
```

1. Use double assertion `as unknown as X` to bypass TypeScript type checking in your tests

=== "handlerDecorator.ts"

```typescript
--8<-- "examples/snippets/parser/handlerDecorator.ts"
```

=== "schema.ts"

```typescript
--8<-- "examples/snippets/parser/schema.ts"
```

This also works when using `safeParse` option.

=== "handlerSafeParse.test.ts"

```typescript hl_lines="21-29 35 45"
--8<-- "examples/snippets/parser/unitTestSafeParse.ts"
```

1. Use double assertion to pass expected types to the handler

=== "handlerSafeParse.ts"

```typescript
--8<-- "examples/snippets/parser/handlerSafeParseDecorator.ts"
```

=== "schema.ts"

```typescript
--8<-- "examples/snippets/parser/schema.ts"
```
20 changes: 20 additions & 0 deletions examples/snippets/parser/handlerDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Context } from 'aws-lambda';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { parser } from '@aws-lambda-powertools/parser';
import { Logger } from '@aws-lambda-powertools/logger';
import { orderSchema, type Order } from './schema.js';

const logger = new Logger();

class Lambda implements LambdaInterface {
@parser({ schema: orderSchema })
public async handler(event: Order, _context: Context): Promise<number> {
logger.info('Processing event', { event });

// ... business logic
return event.id;
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
36 changes: 36 additions & 0 deletions examples/snippets/parser/handlerSafeParseDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Context } from 'aws-lambda';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { parser } from '@aws-lambda-powertools/parser';
import { Logger } from '@aws-lambda-powertools/logger';
import { orderSchema, type Order } from './schema.js';
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import type {
ParsedResult,
EventBridgeEvent,
} from '@aws-lambda-powertools/parser/types';

const logger = new Logger();

class Lambda implements LambdaInterface {
@parser({
schema: orderSchema,
envelope: EventBridgeEnvelope,
safeParse: true,
})
public async handler(
event: ParsedResult<EventBridgeEvent, Order>,
_context: Context
): Promise<number> {
logger.info('Processing event', { event });
if (event.success) {
// ... business logic
return event.data.id;
} else {
logger.error('Failed to parse event', { event });
throw new Error('Failed to parse event');
}
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
7 changes: 6 additions & 1 deletion examples/snippets/parser/safeParseDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
EventBridgeEvent,
} from '@aws-lambda-powertools/parser/types';
import { Logger } from '@aws-lambda-powertools/logger';
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';

const logger = new Logger();

Expand All @@ -26,7 +27,11 @@ const orderSchema = z.object({
type Order = z.infer<typeof orderSchema>;

class Lambda implements LambdaInterface {
@parser({ schema: orderSchema, safeParse: true }) // (1)!
@parser({
schema: orderSchema,
envelope: EventBridgeEnvelope,
safeParse: true,
}) // (1)!
public async handler(
event: ParsedResult<EventBridgeEvent, Order>,
_context: Context
Expand Down
4 changes: 3 additions & 1 deletion examples/snippets/parser/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ const orderSchema = z.object({
optionalField: z.string().optional(),
});

export { orderSchema };
type Order = z.infer<typeof orderSchema>;

export { orderSchema, type Order };
31 changes: 31 additions & 0 deletions examples/snippets/parser/unitTestDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Context } from 'aws-lambda';
import type { Order } from './schema.js';
import { handler } from './decorator.js';

describe('Test handler', () => {
it('should parse event successfully', async () => {
const testEvent = {
id: 123,
description: 'test',
items: [
{
id: 1,
quantity: 1,
description: 'item1',
},
],
};

await expect(handler(testEvent, {} as Context)).resolves.toEqual(123);
});

it('should throw error if event is invalid', async () => {
const testEvent = { foo: 'bar' };
await expect(
handler(
testEvent as unknown as Order, // (1)!
{} as Context
)
).rejects.toThrow();
});
});
50 changes: 50 additions & 0 deletions examples/snippets/parser/unitTestSafeParse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Order } from './schema.js';
import type { Context } from 'aws-lambda';
import { handler } from './safeParseDecorator.js';
import {
ParsedResult,
EventBridgeEvent,
} from '@aws-lambda-powertools/parser/types';

describe('Test handler', () => {
it('should parse event successfully', async () => {
const testEvent = {
version: '0',
id: '6a7e8feb-b491-4cf7-a9f1-bf3703467718',
'detail-type': 'OrderPurchased',
source: 'OrderService',
account: '111122223333',
time: '2020-10-22T18:43:48Z',
region: 'us-west-1',
resources: ['some_additional'],
detail: {
id: 10876546789,
description: 'My order',
items: [
{
id: 1015938732,
quantity: 1,
description: 'item xpto',
},
],
},
};

await expect(
handler(
testEvent as unknown as ParsedResult<EventBridgeEvent, Order>, // (1)!
{} as Context
)
).resolves.toEqual(10876546789);
});

it('should throw error if event is invalid', async () => {
const testEvent = { foo: 'bar' };
await expect(
handler(
testEvent as unknown as ParsedResult<EventBridgeEvent, Order>,
{} as Context
)
).rejects.toThrow();
});
});