Middy, Typescript, and Jest in a monorepo

1.4k Views Asked by At

I've set up a Typescript project using Lerna and yarn workspace. This project includes an AWS API Gateway API that is backed by several Lambda functions that use Middy for things like CORS and handling timeouts. I prefer to have unit tests for my code, so I wrote tests for these Lambda functions using Jest, and I run them using ts-jest. However, when I run the unit tests (the only one I've written so far) for one of my Lambdas, I get the error below.

I've tried adding "esmoduleInterop": true (as suggested here) to the compilerOptions section of tsconfig.json in the package directory. I tried adding it to the root tsconfig.json. I tried a custom tsconfig.json for Jest and adding it there. Nothing. What am I doing wrong?

Error:

 FAIL  packages/announcer/test/api/list-non-compliant-rules.test.ts
  ● Test suite failed to run

    TypeError: core_1.default is not a function

       97 | }
       98 |
    >  99 | export const handler = middy(main, {
          |                             ^
      100 |   timeoutEarlyInMillis: TIMEOUT,
      101 |   timeoutEarlyResponse: () => {
      102 |     Log.error(`Timeout of ${TIMEOUT}ms exceeded`)

      at Object.<anonymous> (lambda/api/list-non-compliant-rules.ts:99:29)
      at Object.<anonymous> (test/api/list-non-compliant-rules.test.ts:4:36)

I'm importing Middy like so:

import middy from "@middy/core"

And I'm using it like so:

export const handler = middy(main, {
  timeoutEarlyInMillis: TIMEOUT,
  timeoutEarlyResponse: () => {
    Log.error(`Timeout of ${TIMEOUT}ms exceeded`)
    return internalServerError(new Error("Timeout"))
  },
})
  .use(cors())
  .use(httpErrorHandler())
  .use(
    httpContentNegotiation({
      availableLanguages: ["en-US"],
      availableMediaTypes: ["application/vnd.api+json"],
    })
  )

Package tsconfig.json:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "types": ["node", "jest"],
    "esModuleInterop": true,
  },
  "exclude": ["node_modules", "cdk.out"]
}

Root tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "lib": ["es2018"],
    "declaration": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "inlineSourceMap": true,
    "inlineSources": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization": false,
    "typeRoots": ["./node_modules/@types"],
    "esModuleInterop": true
  },
}
1

There are 1 best solutions below

0
On

This could be due to the API gateway event not being populated enough.

e.g. Just specifying

{
 "headers": {
 "Content-Type": "application/json"
 },
  "body": "{\"ResendEmail\":{\"winId\": \"jr-1234-1234-1234\", \"timestamp\": 1234, \"message\": \"A canned message\"}}"
}

Isn't sufficient.

Specify the whole event

const defaultEvent: APIGatewayProxyEvent = {
  httpMethod: 'post',
  headers: {Authorization: "dummyToken"},
  body: "dummyBody",
  isBase64Encoded: false,
  path: '/change-expiry-elapsed-days',
  multiValueQueryStringParameters: null,
  multiValueHeaders: null,
  pathParameters: null,
  queryStringParameters: null,
  stageVariables: null,
  requestContext: null,
  resource: ''
}

const defaultEvent2: APIGatewayProxyEvent = {
 ...defaultEvent,
 ...mockCall,
};

Then call

describe('Resend email', function () {
  beforeEach(() => {
    ddbMock.reset()
    jest.restoreAllMocks();
  });

  test('verifies successful response', async () => {
   const result = await main(defaultEvent2, defaultContext) as 
   ResponseTest
   expect(result).toEqual({});
});

The context I'm using also has

getRemainingTimeInMillis: () => 0,

As per

const defaultContext: Context = {
  callbackWaitsForEmptyEventLoop: false,
  functionName: 'test',
  functionVersion: '1',
  invokedFunctionArn: 'arn',
  memoryLimitInMB: '1',
  awsRequestId: 'aws',
  logGroupName: 'log',
  logStreamName: 'stream',
  getRemainingTimeInMillis: () => 0,
  done: () => {
  },
    fail: () => {
  },
    succeed: () => {
  }
}

Further, the call does model the input. If the input is json encoded string, then middy will parse 'body' if you apply

const defaultEvent: APIGatewayProxyEvent = {
httpMethod: 'post',
headers: {
    authorization: authorizationSample,
    "Content-Type": "application/json",
},
body: JSON.stringify({ApproveWinner: apiParamsBasic}),

If "Content-Type" is omitted, then the JSON in the body will be passed to the handler function as a string. With Content-Type it will be parsed into JSON.