Jest: Simplifying JavaScript Testing

Technology
Jest: Simplifying JavaScript Testing

Jest: Simplifying JavaScript Testing

Technology
September 28, 2023
10 Mins

Testing is a critical aspect of software development, ensuring that your code functions as intended and continues to do so as you make changes. In the world of JavaScript, one testing framework stands out for its simplicity and comprehensive feature set: Jest.

Jest: Simplifying JavaScript Testing

What is Jest?

Jest is an open-source JavaScript testing framework developed by Facebook. It is designed to make testing JavaScript code, whether it’s for web applications, server-side Node.js applications, or anything in between, a breeze. Jest has gained widespread adoption in the JavaScript community due to its user-friendly nature and powerful capabilities.

Key Features of Jest

1. Zero Configuration: One of Jest’s standout features is its “zero configuration” setup. Out of the box, it provides sensible defaults that work for most projects. This means you can start writing tests almost immediately without spending time configuring the testing environment.

2. Fast and Parallel Execution: Jest is built to be fast. It can run tests in parallel, optimizing test suite execution time, and providing quick feedback to developers. This becomes particularly valuable as your codebase grows.

3. Snapshot Testing: Snapshot testing is a unique feature of Jest. It allows you to capture the output of a component or function and compare it to a previously saved “snapshot.” This is incredibly useful for detecting unexpected changes in your UI or data structures.

4. Mocking: Jest provides a robust mocking system for isolating the code you’re testing from external dependencies or modules. This helps you focus on testing the specific functionality you’re interested in, without worrying about the behavior of external components.

5. Async Testing: Asynchronous code is prevalent in JavaScript, and Jest handles it seamlessly. It supports async/await, Promise testing, and various mechanisms for dealing with asynchronous operations.

6. Code Coverage: Jest can generate code coverage reports, showing you which parts of your code are covered by tests. This helps you identify areas that need more testing and ensures that your test suite is comprehensive.

7. Custom Matchers: Jest allows you to create custom matchers for your test assertions. This tailoring your tests to your specific use cases makes them more expressive and readable.

8. Test Suites: Organize your tests into test suites and test cases, making it easy to manage and run specific sets of tests. This organization helps keep your test code clean and maintainable.

9. Continuous Integration (CI): Jest integrates smoothly with continuous integration and continuous deployment (CI/CD) pipelines. It can be configured to run tests automatically whenever code changes are pushed, ensuring that new code doesn’t introduce regressions.

10. Community and Ecosystem: Being backed by Facebook and with a large community of contributors, Jest has a rich ecosystem of plugins and extensions. This means that if you have a specific testing need, there’s likely a Jest plugin for it.

Getting Started with Jest

To get started with Jest, you need to install it (usually via npm or yarn) and create your test files with a .test.js or .spec.js extension.

npm install --save-dev jest

Jest will automatically find and run these tests. Writing tests in Jest is straightforward and typically follows the "Arrange, Act, and Assert" pattern.

Here’s a simple example of a Jest test:

export const getSum = (a: number, b: number) => { // in index.ts return a + b; }

    import { getSum } from '../index.js'
    test('adds 1 + 2 to equal 3', () => {
     // in getSum.test.ts
     expect(getSum(1, 2)).toBe(3);
    });
    

     PASS ./sum.test.js
     ✓ adds 1 + 2 to equal 3 (5ms)
    

How to use Jest to test AWS Lambda Functions?

Let’s say we have a service say UserService, we are using Serverless Framework to build resources on the top of AWS provider :

      service: userservice    # Serverless.yml
      package:
          patterns:
              - ../config/**
      plugins:
          - serverless-plugin-typescript
          - serverless-offline
          - serverless-prune-plugin 
      provider:
          name: aws
          versionFunctions: false
          apiGateway:
              minimumCompressionSize: 1024
          runtime: nodejs18.x
          region: ap-south-1
          lambdaHashingVersion: 20201221
          environment:
              NODE_ENV: ${env:NODE_ENV, 'PreProd'}
          timeout: 30
          stage: Prod
          iam:
              role:
                   .....
      custom:
          prune:
              automatic: true
              number: 1
      functions:
          getUser:
              handler: src/index.getUser
    

     // in src/index.ts
     export const getUser = async (event:any,context:any,callback:any) =>{
         try{
             const {userId}=event; 
             if(!userId){
                 throw new BadRequestError('Invalid Input');
              }
             // return the user from Db with statusCode 200
          }catch{
             console.error('Failed to get User', error);
           }
     

So far we have our getUser function and a lambda in serverless.yml

So when we deploy our Service, Serverless will create a Stack with the name “userService-prod”, now we write some test data in our test-data folder 1) Invalid userId case 2) Valid userId case 3) userId is missing

{ // invalid-user-id.json "userId": // Expected Error 404 (Resource not found) }

{ // valid-user-id.json "userId": // Expected Error 200 (Success) }

{ // missing-user-id.json // missing userId or empty userId // Expected Error 400 (Bad Request) }

Jest Setup :

{ // package.json ..... "scripts": { ..... "test": "NODE_ENV=PreProd jest" }, ..... }

We create the file jest.config.ts

{ // jest.config.ts preset: 'ts-jest', testEnvironment: 'node', // environment collectCoverage: true, coverageThreshold: { global: { branches: 10, functions: 10, lines: 10, statements: 10 } } }

we create a Jest-setup-env.ts file:

This code fetches environment variables from an AWS Lambda function configuration and makes them available in the Node.js environment for further use, likely for configuration and initialization purposes.

export const setUpEnv = async (functionName: string) => { // setting environment required for the test function/test suite };

We add all test suits in __test__ folder like <function>.test.ts:

  • getUser.test.ts file will look something like :
 import { test, beforeAll, expect, describe, jest } from '@jest/globals';
 import { getUser } from '../../src/index';
 import { setUpEnv } from '../../set-up-env';
 beforeAll(async () => {
     await setUpEnv('getUser'); // set the environment for getUser Lambda function
     jest.spyOn(console, 'log').mockImplementation(() => {}); // ignore internal logs
     jest.spyOn(console, 'info').mockImplementation(() => {});
     jest.spyOn(console, 'warn').mockImplementation(() => {});
 });

 describe('getUser', () => {
     test.concurrent('should return an error if userId is empty or with a 400 statusCode', async () => {
         const input = require('../../test-data/missing-user-id.json'); // input data from test-data folder
         await getUserWorkplaces(input, undefined, (error, response: any) => {
             expect(response.statusCode).toBe(400);
         });
     });
     test.concurrent('should return success if the correct userId is provided', async () => {
         const input = require('../../test-data/valid-user-id.json'); // input data from test-data folder
         await getUserWorkplaces(input, undefined, (error, response: any) => {
             expect(response.statusCode).toBe(200);
         });
     });
     test.concurrent('should return an error if an invalid userId is provided with statusCode 404', async () => {
         const input = require('../../test-data/valid-user-id.json'); // input data from test-data folder
         await getUserWorkplaces(input, undefined, (error, response: any) => {
             expect(response.statusCode).toBe(404);
         });
     });
 });
    

Voilà, we have written our test cases and are good to go, just run :

npm run test

Automating Jest Tests with AWS CodeBuild 🚀

Automating Jest tests with AWS CodeBuild ensures robust testing before each release.

Prerequisites

Before getting started, make sure you have the following prerequisites in place:

  1. An AWS account with access to AWS services.
  2. Your application code is hosted in an AWS CodeCommit repository or any other code repository supported by AWS CodeBuild.

Create a Build Specification File

In your project’s root directory, create a buildspec.yml file that defines the build steps, including installing dependencies and running Jest tests. Here's a basic example:

Assuming that your AWS CodeBuild release configuration is already established, emphasizing the execution of Jest tests within this pre-existing framework.

version: 0.2 phases: install: runtime-versions: nodejs: 14 commands: - npm install # Install project dependencies pre_build: commands: - npm run test build: commands: # Your Build scripts

This buildspec.yml file specifies that CodeBuild should use Node.js 14, install project dependencies using npm install, and run Jest tests with npm test.

You can expect to receive comprehensive test coverage reports as well as clear indications of passed and failed test cases when working with Jest.

It’s important to emphasize that while the code sample provided here demonstrates a basic setup, the Jest framework offers extensive customization options to tailor it to your specific needs.

We highly recommend referring to the Jest documentation for a more in-depth understanding of its configuration capabilities. This article serves as an illustration of Jest’s user-friendliness and its ability to streamline the process of writing and running test cases.

FAQ

Build Apps That Fit Your Business Operations
Build Apps That Fit Your Business OperationsGet Started - It's free