How to jest at node_modules that provide a function?

I've been working on a typeScript program that interacts with an external API. While writing tests for this program, I've encountered challenges in properly mocking the dependency on the external API to analyze the values passed to the API itself.

A simplified snippet of my code that accesses the API looks like this:

const api = require("api-name")();

export class DataManager {
  setup_api = async () => {
    const email = "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b7d2dad6dedbf7c0d2d5c4dec3d299d2cfc3">[email protected]</a>";
    const password = "password";
    try {
      return api.login(email, password);
    } catch (err) {
      throw new Error("Failure to log in: " + err);
    }
  };

Here is how I've set up my test logic:

jest.mock("api-name", () => () => {
  return {
    login: jest.fn().mockImplementation(() => {
      return "200 - OK. Log in successful.";
    }),
  };
});

import { DataManager } from "../../core/dataManager";
const api = require("api-name"")();

describe("DataManager.setup_api", () => {
  it("should login to API with correct parameters", async () => {
    //Arrange
    let manager: DataManager = new DataManager();
    
    //Act
    const result = await manager.setup_api();
    
    //Assert
    expect(result).toEqual("200 - OK. Log in successful.");
    expect(api.login).toHaveBeenCalledTimes(1);
  });
});

The issue I'm facing lies in the failing assertion

expect(api.login).toHaveBeenCalledTimes(1)
. This indicates that the API is indeed being mocked, but I am unable to access the original mock function. I suspect this might be due to replacing login with a NEW jest.fn() when called at the beginning of my test logic. I'm unsure how to prevent this or gain access to the mock function as I'm primarily concerned with verifying the function is called with the correct values rather than specific return results.

The challenge in mocking this library seems related to the way it's imported using

const api = require("api-name")();
, where parentheses are necessary after the require statement. However, I'm uncertain about its implications and impact on testing.

Answer â„–1

In my search for a solution, I stumbled upon an answer within this specific discussion on ts-jest. It appears that ts-jest does not handle the "hoisting" of variables following the naming convention mock*, unlike regular jest behavior. This leads to an error when trying to use a named mock variable before initializing it with the factory parameter in jest.mock().

As mentioned in the thread, using jest.doMock() functions similarly to jest.mock(), but without hoisting the variables to the top of the file. This allows for creating variables prior to mocking out the library.

To achieve a working solution, you can follow this approach:

const mockLogin = jest.fn().mockImplementation(() => {
  return "Mock Login Method Called";
});

jest.doMock("api-name", () => () => {
  return {
    login: mockLogin,
  };
});

import { DataManager } from "../../core/dataManager";

describe("DataManager.setup_api", () => {
  it("should login to API with correct parameters", async () => {
    //Arrange
    let manager: DataManager = new DataManager();

    //Act
    const result = await manager.setup_api();

    //Assert
    expect(result).toEqual("Mock Login Method Called");
    expect(mockLogin).toHaveBeenCalledWith("[email protected]", "password");
  });
});

This workaround is primarily useful when working with ts-jest, as transforming jest typescript tests using babel will support the expected hoisting behavior. While updates to ts-jest may alter this in the future, utilizing jest.doMock() provides a satisfactory temporary solution.

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

Vuex - modify store state without changing the original object

Currently, I am encountering an issue where my attempt to duplicate a store object for local modification is causing the original store object to also be changed. The specific method I am using is ...mapState["storeObject"] To illustrate, here is a breakd ...

Is there a way to exclude certain URLs from the service worker scope in a create react app, without the need to eject from the project?

Is there a way to remove certain URLs from a service worker scope in create-react-app without needing to eject? The service worker is automatically generated, and it seems impossible to modify this behavior without undergoing the ejection process. ...

Steps to access arrays that are embedded in a file and are loaded asynchronously

https://gyazo.com/e80eea9f69c0cbb52cc7929d0a46ea2f The name of the object is totalCount. How can I retrieve the count from it? Object.result was my first attempt, but it returned undefined. For my second attempt: var count = totalCount.resu ...

A TypeScript example showcasing a nested for-of loop within several other for loops

Is it possible to generate values from an Array of objects in the following way? const arr = [ { type: "color", values: [ { name: "Color", option: "Black", }, { name: "C ...

Rebooting checkbox data with AJAX on MVC 5 form submission

Within my application, I am utilizing an AJAX BeginForm: @using (Ajax.BeginForm("DeletePages", "Home", new AjaxOptions { HttpMethod = "POST", OnSuccess = "OnSuccessDelete", OnFailure = "OnFailureDelete" }, new { id = "ToolBarActionsForm" })) { @Html.A ...

What causes an interface to lose its characteristics when a property is defined using index signatures?

Here's the code snippet I'm having trouble with, which involves tRPC and Zod. import { initTRPC, inferRouterOutputs } from '@trpc/server'; import { z } from "zod"; const t = initTRPC.create(); const { router, procedure } = t; ...

How come my date computed property does not update reactively when changes occur?

I have a Date object in my data, and I need to convert the date into a string for a date picker component in Vuetify. The initial date is being read and displayed correctly. I am able to set the date as well - when I set a code breakpoint, I can see the ...

What process does JavaScript use to interpret indexing?

function highlightRow(searchRow) { if(searchRow != null) { var oRows = document.getElementById('Table').getElementsByTagName('tr'); //use row number to highlight the correct HTML row ...

Pressing the confirm button will close the sweet alert dialog

When pressing the confirm button, the swal closes. Is this the intended behavior? If so, how can I incorporate the loading example from the examples? Here is my swal code: <swal #saveSwal title="Are you sure?" text ="Do you want to save changes" cancel ...

Setting up your Angular2-Typescript application to run smoothly in VS Code

I'm feeling quite lost here, could someone please help me make sense of this configuration? launch.json "configurations": [ { "name": "Launch", "type": "node", "request": "launch", "program": " ...

Displaying and concealing a subcomponent within a parent element for a set duration of time

I have a notification component that is displayed as a child component in the parent component after a button click. It automatically hides after a certain number of seconds. Here is the code I have developed: MyCode Parent component: <button (click)= ...

Sorting elements in jQuery UI by connecting them together

I'm faced with a challenge where I have two columns containing multiple rows/divs each. My goal is to use jQuery UI Sortable in such a way that when I drag a div in the left column, the corresponding row/div in the right column will also be sorted acc ...

What are the steps to showcase the content of a Typescript file on an HTML webpage?

We are looking to create a sample gallery with source code examples. Including typescript, html, and HTML files to showcase similar to the Angular.io component samples pages. Is there a way to extract the source code from a typescript file within our pro ...

The return value from vue-query is ObjectRefImpl, not the actual data

Greetings to the Vue.js community! As a newcomer to Vue.js, I am seeking guidance on fetching data using vue-query, Vue.js 3, and the composition API. The data returned to me is ObjectRefImpl, but when I try to print the values, I encounter the error: "Pro ...

Express middleware for handling errors with Node.js router

My application structure is laid out as follows: - app.js - routes ---- index.js The ExpressJS app sets up error handlers for both development and production environments. Here's a snippet from the app.js file: app.use('/', routes); // ro ...

Having trouble converting a string to an array in JS? Splitting doesn't seem to be doing

I encountered an issue where I am reading data from a CSV file, storing the innerHTML to a variable (named content), which the browser recognizes as a string. However, when attempting to convert it to an array, I am facing difficulties. I attempted to use ...

Struggling to create a line break within an SVG using the <tspan> element?

I have a pair of text lines that are wrapped in <tspan> tags. <tspan dy="-11.7890625">welcome</tspan> <tspan dy="16.8" x="285.75">text</tspan> I am trying to add a line break between them, but the <br> tag is not worki ...

Unable to switch checkbox state is not working in Material UI React

I am experiencing an issue with the Material UI checkbox component. Although I am able to toggle the state onCheck in the console, the check mark does not actually toggle in the UI. What could be causing this discrepancy? class CheckboxInteractivity exten ...

The date format adjustments vary depending on the web browser being used

I have a JavaScript function that displays the date in the browser, but the format changes depending on the browser. For example, when I open my project in Chrome, the format is 4/30/2015, but when I open it in IE, it's displayed as 30 April, 2015. Ho ...

Ensuring Consistent Height for Bootstrap Card Headers

I am currently working with bootstrap cards on two different web pages. The challenge I am facing is that on one page, the header text has a fixed height so I can easily match their card-header height using min-height. However, on the second page, the card ...