Serverless Framework API End-to-End Testing using Jest and Typescript
Tags: Serverless, Typescript, integrationtesting
Published: 2020-05-11 11:22:33

## Overview

Test your AWS Typescript Serverless API's using Jest

Sample code is [here](https://github.com/rudijs/serverless-e2e-typescript-example)

If you find any typo's or cut-n-paste errors or mistakes please let me know.

Please also comment if you have any improvement suggestions.

The follow steps and examples were created on April 15th 2020 using:

- Node v12.16.1
- and Serverless Framework versions:

```
Framework Core: 1.67.3
Plugin: 3.6.6
SDK: 2.3.0
Components: 2.29.1
```

## Setup from scratch

The following steps are all command line on a Unix platorm, please adjust for your platorm where required.

Create a new directory and chnage to it (for example):

`mkdir serverless-e2e-typescript-example`

`cd serverless-e2e-typescript-example`

Create an AWS Lambda serverless API:

`npx serverless create --template aws-nodejs-typescript --name api`

Here's an example of the output:

```
❯ npx serverless create --template aws-nodejs-typescript --name api
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.67.3
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs-typescript"
```

Install the node dependencies

`npm install`

Edit the `serverless.yml` file so that we can set a default stage and region.

Under the `provider` section add:

```
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'us-east-1'}
```

Lets make our API endpoint output only a message and not echo the Lambda `input`

Edit `handler.ts`, comment out line 9

`// input: event`

Deploy the API to the 'dev' stage.

Example deployment command and output:

```
> npx serverless --stage dev deploy
Serverless: Bundling with Webpack...
Time: 394ms
Built at: 04/15/2020 1:38:22 PM
         Asset      Size  Chunks                   Chunk Names
    handler.js  1.28 KiB       0  [emitted]        handler
handler.js.map  5.27 KiB       0  [emitted] [dev]  handler
Entrypoint handler = handler.js handler.js.map
[0] ./handler.ts 316 bytes {0} [built]
[1] external "source-map-support/register" 42 bytes {0} [built]
Serverless: Package lock found - Using locked versions
Serverless: Packing external modules: source-map-support@^0.5.10
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service api.zip file to S3 (289.14 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................
Serverless: Stack update finished...
Service Information
service: api
stage: dev
region: us-east-1
stack: api-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://driyuairb6.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
  hello: api-dev-hello
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
```

Lets call the new API endpoint, copy the `hello` endpoint from your deployment:

```
❯ curl https://driyuairb6.execute-api.us-east-1.amazonaws.com/dev/hello
{
  "message": "Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!"
}
``
```

Great! Our new typescript API endpoint is working, lets setup the end-to-end testing.

Install test dependencies:

`npm i -D jest ts-jest @types/jest axios`

Create a testing directory and Jest config file

```
mkdir e2e
touch e2e/jest.config.js
```

This is how your `jest.config.js` file should be:

```
module.exports = {
  testEnvironment: "node",
  transform: {
    "^.+\\.tsx?$": "ts-jest",
  },
}
```

You can create any testing file structure you prefer but for this example we'll be creating files of the test functions then importing them into a single test file describing all the tests in order and assigning them with the imported functions.

Create a test file for the `hello` API endpoint, I prefer to prefix with a number as it's common to test API endpoints in sequence:

Create the file `100_hello.ts` with the code content:

```
import axios from "axios"

const url = process.env.URL

export const helloTest = () => {
  test("should reply success", async () => {
    const res = await axios.get(`${url}/hello`)
    expect(res.status).toEqual(200)
    expect(res.data.message).toMatch(/Your function executed successfully!/)
  })
}
```

Create the test suite runner file `index.test.js` with the code content:

```
import { helloTest } from "./100_hello"

describe("hello", helloTest)
```

Lets run the end-to-end test manually first, then we'll create an npm script to simplify it:

```
 URL=https://driyuairb6.execute-api.us-east-1.amazonaws.com/dev ./node_modules/.bin/jest -c e2e/jest.config.js  --runInBand --bail
ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
 PASS  e2e/index.test.ts
  hello
    ✓ should reply success (474ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.059s, estimated 2s
Ran all test suites.
```

Yay! Our end-to-end test passes.

Let's fix that ts-jest warning by adding `"esModuleInterop": true` in the tsconfig.json file

`"esModuleInterop": true`

Run the tests manually again:

```
URL=https://driyuairb6.execute-api.us-east-1.amazonaws.com/dev ./node_modules/.bin/jest -c e2e/jest.config.js --runInBand --bail
 PASS  e2e/index.test.ts
  hello
    ✓ should reply success (489ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.96s, estimated 2s
Ran all test suites.
```

Next lets create an npm script in `package.json`:

```
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"e2e": "jest -c e2e/jest.config.js --runInBand --bail e2e"
},
```

Now we can run our tests with:

```
URL=https://driyuairb6.execute-api.us-east-1.amazonaws.com/dev npm run e2e

> api@1.0.0 e2e /home/rudi/projects/serverless-e2e-typescript-example
> jest -c e2e/jest.config.js --runInBand e2e

 PASS  e2e/index.test.ts
  hello
    ✓ should reply success (474ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.001s
Ran all test suites matching /e2e/i.
```

Finally lets clean up with:

`npx serverless --stage dev remove`

```
npx serverless --stage dev remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
.............
Serverless: Stack removal finished...
```

## Credits

- [Folder structure suggestion for sequential test execution: @sahil5695](https://github.com/facebook/jest/issues/6194#issuecomment-419837314)
- [Jest Project Documentation](https://jestjs.io/docs/en/configuration)