What is the best way to simulate DynamoDB promise functionality with Jest?

Looking for a solution without using any libraries (as they haven't worked in my case). How can I make it work for that promise returned by the dynamodb query?

Check out the code to be tested below:

  const isUrl = require('is-url');
const AWS = require('aws-sdk');
const { nanoid } = require('nanoid/async');
const express = require('express');

const router = express.Router();
const dynamoDb = new AWS.DynamoDB.DocumentClient();

// URL from users

router.post('/', async (req, res) => {
  // urlId contains converted short url characters generated by nanoid

  const urlId = await nanoid(8);
  const { longUrl } = req.body;

  // Veryfying url Format using isUrl, this return a boolean
  const checkUrl = isUrl(longUrl);
  if (checkUrl === false) {
    return res.status(400).json({ error: 'Invalid URL, please try again!!!' });
  }

  const originalUrl = longUrl;
  const userType = 'anonymous'; // user type for anonymous users
  const tableName = 'table-name'; // table name for storing url's

  const anonymousUrlCheckParams = {
    TableName: tableName,
    Key: {
      userId: userType,
      originalUrl,
    },
  };

  const paramsForTransaction = {
    TransactItems: [
      {
        Put: {
          TableName: tableName,
          Item: {
            userId: userType,
            originalUrl,
            convertedUrl: `https://url/${urlId}`,
          },
        },
      },

      {
        Put: {
          TableName: tableName,
          Item: {
            userId: urlId,
            originalUrl,
          },
          ConditionExpression: 'attribute_not_exists(userId)',
        },
      },
    ],
  };

  try {
    const dynamoDbGetResults = await dynamoDb
      .get(anonymousUrlCheckParams)
      .promise();
    if (
      Object.keys(dynamoDbGetResults).length === 0 &&
      dynamoDbGetResults.constructor === Object
    ) {
      await dynamoDb.transactWrite(paramsForTransaction).promise();
      return res.status(201).json({
        convertedUrl: `https://trimify.awssensei.tk/${urlId}`,
      });
    }
    return res.status(201).json({
      convertedUrl: dynamoDbGetResults.Item.convertedUrl,
    });
  } catch (err) {
    return res
      .status(500)
      .json({ error: 'Unknown Server Error, Please Trimify Again!' });
  }
});

module.exports = router;

And here's the test code:

describe('Test for Authenticated Url', () => {
  beforeEach(() => {
    jest.resetModules();
  });
  test('should return converted url for authenticated user', async () => {
    const mockedDocumentClient = {
      get: jest.fn(),
      transactWrite: jest.fn(),
    };

    const mockedDynamoDB = {
      DocumentClient: jest.fn(() => mockedDocumentClient),
    };
    jest.doMock('aws-sdk', () => ({ DynamoDB: mockedDynamoDB }));

    mockedDocumentClient.get.mockImplementation((params, callback) => {
      const data = {
        Item: {
          convertedUrl: 'xyz',
        },
      };
      callback(null, data);
    });
    mockedDocumentClient.transactWrite.mockImplementation(
      (params, callback) => {
        callback(null);
      }
    );

    const app = require('../app');
    const response = await request(app).post('/auth-ops/convert').send({
      longUrl: 'https://google.com',
      userId: 'google-oauth2|110198332436292901252',
    });
    expect(response.body).toMatchObject({ convertedUrl: 'xyz' });
    expect(response.statusCode).toBe(201);
  });
test('should send 201 when it is new Url', async () => {
    const mockedDocumentClient = {
      get: jest.fn(),
      transactWrite: jest.fn(),
    };

    const mockedDynamoDB = {
      DocumentClient: jest.fn(() => mockedDocumentClient),
    };
    jest.doMock('aws-sdk', () => ({ DynamoDB: mockedDynamoDB }));

    mockedDocumentClient.get.mockImplementation((params, callback) => {
      const data = {};
      callback(null, data);
    });
    mockedDocumentClient.transactWrite.mockImplementation(
      (params, callback) => {
        callback(null);
      }
    );

    const app = require('../app');
    const response = await request(app).post('/auth-ops/convert').send({
      longUrl: 'https://google.com',
      userId: 'google-oauth2|110198332436292901252',
    });
    expect(response.body).not.toBe(undefined);
    expect(response.statusCode).toBe(201);
  });
});

Answer №1

After removing the express-specific code, I have created an example using your logic. I have added 3 comments labeled "CASE A", "CASE B", and "CASE C", which correspond to all branches in your code (e.g., ifs or returns). We will utilize these in our test case.

let AWS = require("aws-sdk");

let dynamoDb = new AWS.DynamoDB.DocumentClient({ apiVersion: "2012-08-10" });

async function main() {
  try {
    const dynamoDbGetResults = await dynamoDb.get("some-loginUrlCheckParams").promise();
    // CASE A
    if (Object.keys(dynamoDbGetResults).length === 0 && dynamoDbGetResults.constructor === Object) {
      let urlId = await dynamoDb.transactWrite("some-paramsForTransaction").promise();
      return {
        convertedUrl: `https://my-url/${urlId}`,
      };
    }
    // CASE B
    return {
      convertedUrl: dynamoDbGetResults.Item.convertedUrl,
    };
  } catch (err) {
    // CASE C
    return { error: "Unknown Server Error, Please Trimify Again!", message: err.message };
  }
}

module.exports = { main };

From here, we need access to .get and .transactWrite per test so we can mock the values returned for each specific case.

To achieve this, you can leverage how Jest mocks and hoist variables starting with mock*.

We create an API to manipulate the returned value for each test case:

let AWS = require("aws-sdk");

let { main } = require("./main");

let mockDocumentClient = {
  get: {
    promise: jest.fn()
  },
  transactWrite: {
    promise: jest.fn()
  },
};

jest.mock("aws-sdk", () => {
  return {
    DynamoDB: {
      DocumentClient: jest.fn().mockImplementation(() => {
        return {
          get: () => mockDocumentClient.get,
          transactWrite: () => mockDocumentClient.transactWrite,
        };
      }),
    },
  };
});

describe("Test for Authenticated Url", () => {
  it("returns CASE A", async () => {
    mockDocumentClient.get.promise.mockReturnValueOnce({})
    mockDocumentClient.transactWrite.promise.mockReturnValueOnce("test-urlId")
    let test = await main();
    expect(test).toEqual({ convertedUrl: 'https://my-url/test-urlId' })
  });

  it("returns CASE B", async () => {
    mockDocumentClient.get.promise.mockReturnValueOnce({
      Item: {
        convertedUrl: "test-converted-url"
      }
    })
    let test = await main();
    expect(test).toEqual({ convertedUrl: 'test-converted-url' })
  });

  it("returns CASE C", async () => {
    mockDocumentClient.get.promise.mockImplementationOnce(() => {
      throw new Error("test-error")
    })
    let test = await main();
    expect(test).toEqual({ error: 'Unknown Server Error, Please Trimify Again!', message: "test-error" })
  });
});

Lazily load mockDocumentClient into our jest.mock to pass the same mock variable to our test cases and manipulate the returned value effectively.

This approach enables achieving 100% test coverage within the snippet.

I recommend reviewing these two articles for a more detailed explanation from a Jest perspective:

Jest Mock - Calling jest.mock with the module factory parameter

Fix Jest Mock Cannot Access Before Initialization Error

Answer №2

To mimic the dynamodb signature using a mock, you can use the following code snippet as a starting point. Please note that this is a general example and may need to be tailored to match your specific DynamoDB import.

jest.mock('dynamodb', () => {
  get: async (data) => jest.fn()
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Displaying a loading screen while a jQuery ajax request is in progress

Currently, I am attempting to display a loading div while waiting for an ajax call to finish. Despite experimenting with various methods, I have not been able to consistently achieve the desired outcome. In my present code, everything functions properly o ...

Issue with loading Babel preset in a monorepo setup

I'm working with a monorepo setup using Lerna and it's structured as follows: monorepo |-- server |-- package1 |-- package2 All packages in the repo make use of Babel. After installing all 3 projects, yarn copied all the required @babe ...

Connect-Domain fails to detect errors in the scenario described below:

I have chosen to implement the connect-domain module (https://github.com/baryshev/connect-domain) in order to streamline error handling within my Express application. Although it generally functions as expected, there is a peculiar issue that arises when ...

Getting the text from an HTML input field requires accessing the value property of the

My goal is to generate a pdf report using php. The user will enter their name in an input box, which will then be passed to the second php page that searches the mysql database. Issue: When the user inputs their name, attempting to retrieve the data (var ...

Get your hands on the base64 image by triggering the save as popup and downloading

I am facing a challenge with downloading a base64 image onto the user's machine. So far, I have attempted the following steps: var url = base64Image.replace(/^data:image\/[^;]+/, 'data:application/octet-stream'); window.open(url); an ...

redux - managing asynchronous storage using key-value pairs

Utilizing associative arrays with redux and storing them in async storage for later retrieval is my current challenge. When using redux, I am able to quickly access the values and efficiently map the content into cards in my react native app. However, aft ...

Tips for eliminating unnecessary module js calls in Angular 9

https://i.sstatic.net/3R7sr.png Utilizing a lazy loading module has been efficient, but encountering challenges with certain modules like all-access-pass and page not found as shown in the image above. Is there a way to effectively remove unused module J ...

What is the most effective way to output data using the response.write method in a Node.js program after retrieving it from a MySQL server?

Currently working on a NodeJS web app and encountering a roadblock. Seeking feedback on my code: var config = require('./config.json'); var mysql = require('mysql'); var http = require('http'); var url = require('url&apo ...

Ways to permit https://* within a content security policy (CSP) configuration

I'm currently incorporating CSP into my website but encountering an issue with the img-src header. I'm using NodeJS and Express to develop the site for my Discord Bot, and I want to revamp it but I've hit a roadblock. ====== This is the co ...

Troubles encountered with the search bar filter functionality, implementing JS/JQuery within a Laravel blade template

Currently, I have a blade template containing a search bar for filtering purposes. The issue I'm encountering is that the filtering functionality doesn't seem to work as expected after removing Angular from the page entirely. The page is set up ...

Displaying XML data in an HTML table

Encountered a challenge while fetching data from an external XML document using JS. Following the w3schools tutorial for AJAX XML, but faced an issue I couldn't resolve. The XML structure is as follows: <root> <document-id> <author ...

Add elements to an array with express, Node.js, and MongoDB

I'm currently learning about the MERN stack and I'm working on creating users with empty queues to store telephone numbers in E.164 format. My goal is to add and remove these numbers from the queue (type: Array) based on API requests. However, I ...

Prevent the Icon in Material UI from simultaneously changing

I'm working on a table where clicking one icon changes all icons in the list to a different icon. However, I want to prevent them from changing simultaneously. Any suggestions on how to tackle this issue? Code: import React from 'react'; im ...

Error: Issue transferring data to controller from Ng-Style

Currently, I am developing an application using Ionic and AngularJS. In this project, I am using ng-repeat to populate the results and want to display different colors based on certain criteria. Initially, I managed to achieve this using inline ng-style c ...

Creating a table in HTML using the innerHTML property

document.getElementById("outputDiv").innerHTML = ""; document.getElementById("outputDiv").innerHTML += "<table border=1 width=100%><tr>"; for(j=1;j<=10;j++) { document.getElementById("outputDiv").innerHTML += "<td align=center>"+St ...

Is there a way to bypass the final function call when using Express Middleware?

In my Node.js project using express, I have a function inside a get route... The function currently includes a simple caching functionality that I coded myself. It queries data from an MSSQL Database and returns it using res.json(data). However, I want to ...

What is the resolution process for importing @angular/core/testing in TypeScript and what is the packaging structure of the Angular core framework?

When using import {Injectable} from '@angular/core';, does the module attribute in package.json point to a file that exports injectable? Also, for the format @angular/core/testing, is there a testing folder within @angular/core that contains anot ...

Building a web proxy with node.js and express

I'm currently in the process of designing a personalized web proxy using JavaScript to allow users to surf the internet through a designated website, similar to this . One challenge I encountered is transferring the response from a URL back to the us ...

Focused on individual characters in a string to implement diverse CSS styles

Is there a way I can apply different CSS classes to specific indexes within a string? For example, consider this string: "Tip. \n Please search using a third character. \n Or use a wildcard." I know how to target the first line with CSS using : ...

Adjusting the size of several images individually with jquery

Currently, I am working on a jQuery script that enables me to resize any image by simply clicking on it. The goal is to have the ability to click on one image and resize it, then click on another image and resize it independently. Here is the code I have b ...