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

How can React Native efficiently retrieve data from multiple APIs simultaneously?

In my current project, I am incorporating multiple APIs that are interlinked with each other by sharing the same data structure... Below is the code snippet: export default class App extends React.Component { constructor(props) { super(props); } ...

Embed a YouTube video within the product image gallery

Having trouble incorporating a YouTube video into my Product Image Gallery. Currently, the main product photo is a large image with thumbnails that change it. You can see an example on my website here. Whenever I attempt to add a video using the code below ...

Relocating sprite graphic to designated location

I am currently immersed in creating a captivating fish animation. My fish sprite is dynamically moving around the canvas, adding a sense of life to the scene. However, my next goal is to introduce food items for the fishes to feast on within the canvas. Un ...

Data within object not recognized by TableCell Material UI element

I am currently facing an issue where the content of an object is not being displayed within the Material UI component TableCell. Interestingly, I have used the same approach with the Title component and it shows the content without any problems. function ...

What is the best way to find an onmouseover element with Selenium in Python?

I've been attempting to scrape a website and encountered an element that reveals information in a bubble when the mouse hovers over it. I am using Selenium for web scraping, but I am unsure how to locate this specific element. After examining the pag ...

Trigger a fixed bottom bar animation upon hover

I have a bar fixed to the bottom of the browser that I want to hide by default. When a user hovers over it, I want the bar to be displayed until they move their cursor away. <!doctype html> <html> <head> <meta charset="utf-8"> &l ...

Organizing DIVs upon website initialization

My website features a layout with three columns: <div id="column1"></div> <div id="column2"></div> <div id="column3"></div> I currently have 3 divs on the webpage: <div id="1">aaa</div> <div id="2">b ...

Currently focused on designing a dynamic sidebar generation feature and actively working towards resolving the issue of 'Every child in a list must have a distinct "key" prop'

Issue Found Alert: It seems that each child within a list needs a unique "key" prop. Please review the render method of SubmenuComponent. Refer to https://reactjs.org/link/warning-keys for further details. at SubmenuComponent (webpack-internal:///./src/c ...

Browser displaying a CSS error: "Invalid property name" while applying pseudo-element :after

I encountered an issue in Chrome and Explorer while attempting to set a CSS property for a pseudo element :after. (I'm trying to create a styled burger nav icon) The error message I received was: 'Unknown property name' This happened wh ...

Steps for accessing req.session in nextServerInit before rendering:1. Start by importing the

Currently, I am in the process of setting up a website using nuxt+express by running 'vue init nuxt-community/express-template' command. The specific feature I am focusing on is "auth". My goal is to maintain the user's information stored co ...

Creating a Dynamic Slideshow on Autopilot

My JavaScript skills are not perfect and I'm feeling a bit lost. I have this code for a slideshow, but I want to make it automatic while also allowing users to navigate between images freely. I'm struggling to figure out how to implement this fun ...

The second parameter of the Ajax request is not defined

Having an issue with my Ajax request in Vue.js where the second parameter is logging as undefined in the console. I've been trying to fix this problem but haven't found a solution yet. This is the code snippet for $.ajax(): //$.ajax() to add ag ...

Deliver XML document to client from an ASP.NET MVC webpage

I need help with an ASP.NET MVC web page that requires user input to create an XML file using a javascript function. After the user enters information and clicks a button, how can I display the XML created by the javascript method? In the .cshtml file: Fo ...

Issue with React event hierarchy

How can I effectively manage state changes in a deep node that also need to be handled by a parent node? Let me explain my scenario: <Table> <Row prop={user1}> <Column prop={user1_col1} /> <Column prop={user1_col2} /> ...

Assign a class to an element depending on its position using JavaScript/jQuery

<ul> <li>Apple</li> <li>Banana</li> <li>Orange</li> </ul> How can I use jQuery to add a class to the second li element in the list without using an existing class or id? ...

Achieving success with the "silent-scroll" technique

I've been struggling to implement the 'scroll-sneak' JavaScript code for quite some time now. This code is designed to prevent the page from jumping to the top when an anchor link is clicked, while still allowing the link to function as inte ...

Oops! You can only have one parent element in JSX expressions

I'm working on creating a password input box, but I keep encountering an error when integrating it into my JS code. Here's the snippet of my code that includes TailwindCSS: function HomePage() { return ( <div> <p className=&q ...

Consistently fluctuating eTag in motion

My Node Express API has a default ETag configuration, but it keeps generating a new ETag every time I test hitting the server, even when the response content remains the same. I've compared the headers and tested with both API and static HTML content ...

Identify the location of the mouse and activate a specific function based

Tracking the x-coordinate of the mouse is crucial in this scenario. When the mouse approaches a specific threshold (250px) towards the left edge of the window, it should trigger the function "openNav." The function should close when the mouse moves away fr ...

How to trigger a file download instead of opening it in a new tab when clicking on a txt or png file in AngularJS

After retrieving my file URL from the backend API, I am trying to enable downloading when the user clicks a button. Currently, the download function works smoothly for Excel files (`.xlsx`), but for text (`.txt`) files or images (`.jpeg`, `.png`), it only ...