AWS SAM CLI Unit testing Lambda Functions NodeJS

2.4k views Asked by At

I am using the SAM CLI to test and deploy AWS Lambda functions. I am trying to run unit tests using Mocha and Chai in NodeJS. The tests are located in the test directory and I can run the tests using the command mocha --recursive.

The problem is I am using environment variables in the tests. My environemnt variables are defined in the template.yaml file for the SAM CLI. It looks something like this:

  RefreshFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/index.handler
      Runtime: nodejs12.x
      MemorySize: 128
      Timeout: 100
      Environment:
        Variables:
          APP_ID: A
          CLIENT_ID: B 
          CLIENT_SECRET: C 
          AWS_REGION_DB: D

They are defined here as when I deploy my function they automatically go with it onto AWS too. It also allows me to use the sam local invoke command which runs my lambda function locally using Docker.

Is there anyway that anyone knows that the sam local invoke command can be used to run test cases locally instaed of running the lambda function? Or how to access my environment variables in the template.yaml file from test cases using mocha and chai?

3

There are 3 answers

2
WK123 On BEST ANSWER

Decided to update this answer as a few people have bookmarked this question.

To get the same setup I have to start with run sam init, select the AWS Quick Start Templates, the run time is nodejs12.x and the application template to use is Quick Start: From Scratch.

After this step you should have a directory structure that looks like this below in your sam application.

-rw-r--r-- 1 user 9596 Apr  9 12:02 README.md
drwxr-xr-x 4 user 4096 Apr  9 12:02 __tests__
-rwxr-xr-x 1 user 828  Apr  9 12:02 buildspec.yml
-rwxr-xr-x 1 user 374  Apr  9 12:02 package.json
drwxr-xr-x 3 user 4096 Apr  9 12:02 src
-rwxr-xr-x 1 user 1545 Apr  9 12:02 template.yml

Run an npm install to install the dependecies which creates your node_modules directory. After this has finished run the following command to install the dotenv module.

npm install dotenv 

Create a file named .env in the root directory. Your root directory should now look like this.

-rw-r--r-- 1   user 9596   Apr  9 12:02 README.md
drwxr-xr-x 4   user 4096   Apr  9 12:02 __tests__
-rwxr-xr-x 1   user 828    Apr  9 12:02 buildspec.yml
drwxr-xr-x 391 user 16384  Apr  9 12:05 node_modules
-rw-r--r-- 1   user 488688 Apr  9 12:05 package-lock.json
-rwxr-xr-x 1   user 374    Apr  9 12:02 package.json
drwxr-xr-x 3   user 4096   Apr  9 12:02 src
-rwxr-xr-x 1   user 1545   Apr  9 12:02 template.yml
-rw-r--r-- 1   user 28     Apr  9 11:06 .env

Now in your .env file create an environment variable like my sample one below.

TEST_ENV_VAR=HELLO_FROM_TEST

In your template.yml file also define an environment variable of the same name but give it a different value so we can see when the two different locations are being read in the next step.

helloFromLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/hello-from-lambda.helloFromLambdaHandler
      Runtime: nodejs12.x
      MemorySize: 128
      Timeout: 100
      Description: A Lambda function that returns a static string.
      Policies:
        - AWSLambdaBasicExecutionRole
      Environment:
        Variables:
          TEST_ENV_VAR: Hello_From_Local_Invoke #HERE WE ARE DEFINING ENV VARS

In the handler file located at src/handlers/hello-from-lambda.js change the code to the following which is reading in the environment varibale TEST_ENV_VAR and returning it.

exports.helloFromLambdaHandler = async () => {
    // Read in the environment variable 
    const message = process.env.TEST_ENV_VAR
    // Log the environment variable out 
    console.info(`${message}`);
    // Return the environment variable so we can verify value in test case 
    return message;
}

In your test handler located at __tests__/unit/handlers/hello-from-lambda.test.js paste the following code. It reads the environment variables from the .env you created earlier using the dotenv module. The test will now pass if the value returned from the helloFromLambdaHandler is equal to the value HELLO_FROM_TEST which is the value we defined for the environment variable TEST_ENV_VAR in the .env file.

// Import all functions from hello-from-lambda.js
const lambda = require('../../../src/handlers/hello-from-lambda.js');
require('dotenv').config(); // HERE WE ARE USING THE dotenv MODULE 

// This includes all tests for helloFromLambdaHandler()
describe('Test for hello-from-lambda', function () {
    // This test invokes helloFromLambdaHandler() and compare the result 
    it('Verifies successful response', async () => {
        // Invoke helloFromLambdaHandler()
        const result = await lambda.helloFromLambdaHandler();
        // The expected result should match the return from your Lambda function.
        // We expect it to be HELLO_FROM_TEST as thats what is in our .env file for the variable TEST_ENV_VAR
        const expectedResult = 'HELLO_FROM_TEST';
        // Compare the result with the expected result
        expect(result).toEqual(expectedResult);
    });
});

Now run the test by running the command npm run test in the root directory, it passes showing that we are reading from the .env for test cases.

$ npm run test

> [email protected] test
> jest --roots __tests__/unit

 PASS  __tests__/unit/handlers/hello-from-lambda.test.js
  Test for hello-from-lambda
    ✓ Verifies successful response (12 ms)

  console.info
    HELLO_FROM_TEST

      at Object.helloFromLambdaHandler (src/handlers/hello-from-lambda.js:5:13)

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

To prove we are reading from the template.yml file for production builds you can invoke a local test by running sam build to build the project and then sam local invoke all from the root directory.

$ sam local invoke
Invoking src/handlers/hello-from-lambda.helloFromLambdaHandler (nodejs12.x)
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-nodejs12.x:rapid-1.12.0.

Mounting /home/warren/lambda/new_test/sam-app/.aws-sam/build/helloFromLambdaFunction as /var/task:ro,delegated inside runtime container
START RequestId: 4de19569-5ebc-1c20-1cec-9a290dd3ef9b Version: $LATEST
2021-04-09T11:29:40.993Z        4de19569-5ebc-1c20-1cec-9a290dd3ef9b    INFO    Hello_From_Local_Invoke
END RequestId: 4de19569-5ebc-1c20-1cec-9a290dd3ef9b
REPORT RequestId: 4de19569-5ebc-1c20-1cec-9a290dd3ef9b  Init Duration: 187.97 ms        Duration: 6.80 ms       Billed Duration: 100 ms                                Memory Size: 128 MB      Max Memory Used: 48 MB

"Hello_From_Local_Invoke"

You can see we now get the value Hello_From_Local_Invoke which is the value we assigned to the environment variable TEST_ENV_VAR in the template.yml file.

Hope this helps!

0
Derrops On

Environment variables are meant to be defined in your environment. You would use other tools like dotenv if you wanted to run locally but change some/all of the environment variables. You can also put these in your package.json in the scripts section with FOO=BAR. As long as there are no secrets in there that will be safe to do.

0
Andresa Martins On

I found out another way of doing it. You can set the environment variable in your own local machine:

MacOS:

export APP_ID=A

Windows:

SET APP_ID=A

Then just run your tests using mocha as usual.