What is the best way to simulate a constructor-created class instance in jest?

Suppose there is a class called Person which creates an instance of another class named Logger. How can we ensure that the method of Logger is being called when an instance of Person is created, as shown in the example below?

// Logger.ts
export default class Logger {
    constructor() {}
    log(m: String) {
        console.log(m);

        // Other operations that are outside testing (e.g., file write).
        throw Error('error');
    }
}

// Person.ts
import Logger from "./Logger";
export default class Person {
    constructor() {
        const logger = new Logger();
        logger.log('created');
    }
    // ...
}

// Person.test.ts
import Person from "./Person";
import Logger from "./Logger";
describe('Person', () => {
    it('calls Logger.log() on instantiation', () => {
        const mockLogger = new Logger();
        getCommitLinesMock = jest
            .spyOn(mockLogger, 'log')
            .mockImplementation(() => {});

        new Person(); // Should call Logger.log() on instantiation.

        expect(getCommitLinesMock).toBeCalled();
    });
});

An alternative approach is to include Logger as a parameter in the constructor like so:

class Person {
    constructor(logger: Logger) {
        logger.log('created');
    }
    // ...
}

However, are there any other methods besides modifying the constructor function to pass this test successfully?

Answer №1

If you want to automatically mock all exports from a specific module, you can use the jest.mock(moduleName, factory, options) method.

For example, by calling jest.mock("./Logger"), the constructor and methods of the Logger module will be replaced with mock functions that return undefined by default. This allows you to easily spy on their behavior.

import Person from "./Person";
import Logger from "./Logger";

jest.mock("./Logger");

describe("Person", () => {
  it("calls the Logger constructor on instantiation", () => {
    new Person();
    expect(Logger).toHaveBeenCalledTimes(1);
  });
});

All mock functions have a special .mock property containing information such as instances created by the mock constructor function when invoked with new.

You can access the instances created by the mock Logger through Logger.mock.instances, enabling you to monitor method calls.

import Person from "./Person";
import Logger from "./Logger";

jest.mock("./Logger");

describe("Person", () => {
  it("calls the Logger constructor and log method on instantiation", () => {
    new Person();
    expect(Logger).toHaveBeenCalledTimes(1);
    const mockLoggerInstance = Logger.mock.instances[0];
    const mockLogMethod = mockLoggerInstance.log;
    expect(mockLogMethod).toHaveBeenCalledTimes(1);
  });
});

Answer №2

Another way to test a class with a method is by using mocking the prototype.

import Person from "./Person";
import Logger from "./Logger";

jest.mock("./Person");

describe("Person", () => {

 it("mock and test Person method", async () => {

  jest.spyOn(Person.prototype, "somePersonMethod").mockImplementation(() => {
      return "testing Person class method";
   });
    
    //expect(...

 });

});

Answer №3

Looking at various solutions, we can find a handy typescript helper called jest.mocked or vi.mocked, which allows for easy casting of a mocked instance as shown below:

vi.mock("./Person");

const mockedInstance = vi.mocked(new Person());
//mockedInstance.xxMethod.mockReturnValue(...)

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

Creating a personalized 404 page in your Angular Project and configuring a route for it

I am currently working on an Angular project that includes a component named 'wrongRouteComponent' for a custom 404 page. Whenever a user enters a non pre-defined route, the 'wrong-route.component.html' should be displayed. However, I a ...

Restricting Meteor Publish to specific user (admin) for all collections

Is there a method to exclusively publish all meteor collections to users with the role of {role: "admin"}? The meteor autopublish package grants database access to all clients. Are there any techniques to utilize the autopublish package while implementing ...

Step-by-step guide to selecting a specific point on an HTML5 canvas using Python's selenium webdriver

Looking to automate interactions with a simple HTML5 application on a website using Selenium webdriver in Python with Firefox on Linux. The challenge is clicking a button on an HTML5 canvas, then dragging one or two objects around the canvas post-button cl ...

Center-align the text in mui's textfield

What I'm looking for is this: https://i.stack.imgur.com/ny3cy.png Here's what I ended up with: https://i.stack.imgur.com/vh7Lw.png I attempted to apply the style to my input props, but unfortunately, it didn't work. Any suggestions? Than ...

TypeScript: By providing a variable CLASS as an argument to a function, automatically determine the return type to be an object of the specified class without any additional information

I am looking to create a function that accepts actual class objects themselves as arguments (within an object containing multiple arguments), with the return type of the function being inferred to be an instance of the class provided as the argument. In t ...

Deciphering JSON strings using JavaScript

Here is a string that I am trying to parse using Json: {\"description\": \"PSY - Gangnam Style (\\uac15\\ub0a8\\uc2a4\\ud0c0\\uc77c) \\n\\u25b6 NOW available on iTunes: h ...

Accessing the element within an ion-tab using document.getElementById

Within my ion-view, I have ion-tabs containing a canvas element. However, when attempting to retrieve the canvas using document.getElementById('photoCanvas'); I receive 'undefined'. Here is the code snippet: HTML: <ion-view ...

The challenge with the mousewheel function in THREE.js Editor

Attempting to create a basic scene in the THREE.js Editor. Using the built-in Script editor, all control functions seem to be functioning correctly except for the mousewheel (I've tried mousedown, mousemove, etc.). I even attempted to add a listener ...

Retrieve the Corresponding Content Inside the Div Element

I have a div with text that needs to be matched with comma-separated values: <div class="val"> This is paragraph 1 </div> <div class="val"> This is paragraph 2 </div> Using an Ajax call, I retrieve the ...

What is the best way to retrieve the value of a nested function in JavaScript?

I am currently working on a project that involves a function. function findParentID(parentName) { Category.findOne({ categoryName: parentName }, function (err, foundParent) { var parentID = foundParent.categoryID;    return parentID;<br> } ...

Attempting to flip the flow of marquee loop in javascript

I am currently modifying this code to create a left-to-right marquee instead of the original right-to-left one. However, after successfully changing the direction, the text no longer loops as it did originally. I'm stuck and can't seem to figure ...

Tips for obtaining the combined outcome of multiple arrays (3 to 5 arrays) in JavaScript

How can we transform an array of objects with nested arrays into a new array of objects with mixed values? Consider the following input: var all = [ { name: "size", value: [20, 10, 5], }, { name: "color", value: [ ...

Performing Iterations in Angular 2 with Immutable.js (utilizing the *ngFor directive)

Struggling with Angular 2 and Immutable JS - having issues with a simple for-loop in my template. Tried both old and new syntax without success. <div *ngFor='#filter of filterArray' class='filter-row'> <div class='row-t ...

Issue encountered when attempting to remove a specific element from a MongoDB array by utilizing the filter function

I have been struggling to find a solution here as I cannot get the desired outcome using $pull because the array values I am working with do not contain 'mongo_id'. The situation is that I am attempting to delete a specific comment from a partic ...

Tips for triggering animation only when the element is in the viewport

I'm currently developing in React and facing a challenge where I need to trigger a fade animation for an element only when it becomes visible on the screen. The issue is that right now, the animation plays as soon as the page loads, which defeats the ...

The IE9 confirmation dialog fails to pause for user response, resulting in automatic postback before user input is received

Behind the Scenes btnNext.Attributes.Add("onclick", " return Verification(this,'" + GetLocalResourceObject("message").ToString() + "'); ") .ASPX Page [Within javascript tags] function Verification(source, message) { var dialog = '< ...

The voracious nature of the `+` and `*` operators

There is a variable, const input = "B123213"; When using the following regex pattern, const reg = /\d+/; and executing String match function, console.log(input.match(reg)); The output returned is 123213, illustrating that the expression is gree ...

Is there a way to identify the duplicated input element values using jquery?

Just starting out in the world of web development and jQuery. I have an input element that has been bound with a blur event. Here's the code snippet: // Here are my input elements: <input class="input_name" value="bert" /> <input class="inp ...

Transferring image data to a different webpage

Currently, I am facing an issue with obtaining image data from a camera and photo album on a mobile device. Although I have successfully retrieved the chosen image using the provided code snippet below, my dilemma lies in transferring this image data to an ...

Using Angular.JS to iterate over a nested JSON array using ng-repeat

I am currently working on a People service that utilizes $resource. I make a call to this service from a PeopleCtrl using People.query() in order to retrieve a list of users from a json api. The returned data looks like this: [ { "usr_id" : "1" ...