How can we use Jest to spy on classes instantiated within a constructor?

I have the following code snippet:

async myFunction(paramA?: string): Promise<void> {
    if (paramA === undefined) {
      paramA = this.generateRandomString();
    }
    this.funnel.loginFunnel(Status.Pending);
    await this.tracker.flush();
    this.service.call(this.name, paramA, code);
  }

I want to test that loginFunnel is called with status pending and the service is called with paramA. However, these classes are initialized in the constructor:

constructor(params: Params) {
    this.tracker = new Tracker(params);
    this.service = new Service(params, this.tracker);
  }

How can I spy on them using Jest? This is pure JavaScript, not related to React or any other similar framework.

I've tried multiple approaches but haven't been successful...

My latest attempt involved importing the Tracker class from its path:

jest.mock('../tracker');
        service.call();
        expect(Tracker).toHaveBeenCalledTimes(1);

However, the test returned the following error:

expect(received).toHaveBeenCalledTimes(expected)

    Matcher error: received value must be a mock or spy function

    Received has type:  function
    Received has value: [Function Tracker]

Answer №1

Your perspective is well understood and has led me to develop a code snippet that allows for spying on an entire class, constructors included. The implementation of this snippet is straightforward; simply add it to a file and import it whenever necessary.

Below is the TypeScript/ES6 code:

/**
 * spyOn references to classes. Use it with spyOnClass
 */
export const classSpy: any = {};

/**
 * Utility to Spy On all class methods. Not including the constructor
 * @returns a spyOn references to all the class methods
 * includes the methods mockClear and mockReset as convenience
 * to trigger the respective method for all the spies
 */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function spyOnClassMethods(proto: any): any {
    const properties = Object.getOwnPropertyNames(proto);
    const spyHolder: any = {};
    for (const i in properties) { spyHolder[properties[i]] = jest.spyOn(proto, properties[i]); }
    spyHolder.mockClear = (): void => { for (const i in properties) { spyHolder[properties[i]].mockClear(); } };
    spyHolder.mockReset = (): void => { for (const i in properties) { spyHolder[properties[i]].mockReset(); } };
    return spyHolder;
}
// To attend jest.mock problems, the should start with 'mock'
const mocksSpyOnClassMethods = spyOnClassMethods;

/**
 * Utility to Spy On all class methods and its constructor.
 */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function spyOnClass(mockModuleName: string, mockClassName: string): any {
    classSpy[mockClassName] = {};
    jest.mock(mockModuleName, () => {
        const module = jest.requireActual(mockModuleName) as any;
        const mock = {};
        classSpy[mockClassName] = mocksSpyOnClassMethods(module[mockClassName].prototype);
        mock[mockClassName] = jest.fn().mockImplementation(
            (...args: any[]) => {
                const instance = new module[mockClassName](...args);
                classSpy[mockClassName].constructor = mock[mockClassName];
                return instance;
            }
        );
        return { ...module, ...mock };
    });
}

An example of how to use this functionality:

import { classSpy, spyOnClass } from './mock-utils';

// If you import ClassName, this must come before the import.
spyOnClass('module-name', 'ClassName');

import { ClassName } from 'module-name';

test('this', () => {
    doSomethingThatUsesClassName();
    expect(classSpy.ClassName.constructor).toHaveBeenCalled();
    expect(classSpy.ClassName.someMethod).toHaveBeenCalled();
});

I hope that this can be beneficial to you and other users.

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

Mocking a celery task to patch in unit tests for Django with the use of Mock

I am currently attempting to utilize the python mock library in order to patch a Celery task that is triggered when a model is saved within my django application, to verify that it is being executed correctly. Essentially, the task is defined within myapp ...

Issue with the display of JQuery slider within TypeScript Angular directive in C# environment

I am encountering an issue with implementing a JQuery slider in my application. When I use it solely with JQuery, it functions properly. However, when I incorporate it into an angular directive built with typescript, the display is not as expected. https: ...

The default generic type is not a valid assignment

Check out this code snippet: function foo<T>(create: T & ((...args: any[]) => any) = () => { return { type: 'abc' }; }) { ... } An error pops up: The type () => { type: string; } cannot be assigned to type T But when you ...

The 'SockJS' export is missing from the sockjs-client module

I'm running into an issue while trying to incorporate SockJS from the sockjs-client library. Currently, I am working with Aurelia and typescript. However, when attempting to import it using import { SockJS } from 'sockjs-client'; I encount ...

Using TypeScript to bypass rest properties in React

Enhanced for TypeScript 2.1 Exciting news! TypeScript 2.1 now fully embraces object spread/rest functionality making workarounds a thing of the past! The Original Inquiry Incorporating JSX spread attributes in React to pass HTML properties from a comp ...

Having trouble retrieving information from Node.js service in AngularJS 2

I am currently expanding my knowledge of Angular and attempting to retrieve data from a node js service using Angular 2 services. When I access the node js services directly from the browser, I can see the results. However, when I attempt to fetch the dat ...

Transitioning from Global Namespace in JavaScript to TypeScript: A seamless migration journey

I currently have a collection of files like: library0.js library1.js ... libraryn.js Each file contributes to the creation of a global object known as "MY_GLOBAL" similarly to this example: library0.js // Ensure the MY_GLOBAL namespace is available if ...

Error encountered during module parsing: Token found was not as expected in Webpack 4

When I run my builds, webpack keeps throwing this error: ERROR in ./client/components/App/index.tsx 15:9 Module parse failed: Unexpected token (15:9) You may need an appropriate loader to handle this file type. | | > const App: SFC = () => ( ...

difficulty with closing nested Mat-dialogBoxes

I am facing an issue with passing data between multiple dialog boxes in my parent raster component. I have a parent raster component that opens the first dialog box, followed by a second dialog box. My goal is to pass data from the last dialog box back to ...

Exploring an array of objects to find a specific string similar to the one being

I recently developed a TypeScript code snippet that searches for objects in a list by their name and surname, not strictly equal: list = list.filter( x => (x.surname + ' ' + x.name) .trim() .toLowerCase() .sear ...

Issue in Angular Material: The export 'MaterialComponents' could not be located in './material/material.module'

I'm relatively new to Angular and I am encountering some difficulties when trying to export a material module. The error message that appears is as follows: (Failed to compile.) ./src/app/app.module.ts 17:12-30 "export 'MaterialComponents&ap ...

Display a single unique value in the dropdown menu when there are duplicate options

Hey there, I'm currently working on retrieving printer information based on their location. If I have multiple printers at the same location, I would like to only display that location once in the dropdown menu. I am aware that this can be resolved at ...

The type 'RefObject<R>' cannot be assigned to the type 'string | ((instance: typeof Room_T | null) => void) | RefObject<typeof Room_T> | null | undefined' in this context

After exporting the Room component with connect in the App component, I noticed that the ref of the Room component does not function properly. Surprisingly, when the connect is removed, everything works as expected. Below is a snippet of the main code for ...

Calculate the sum of elements within an array

I've been attempting to calculate the sum of values in an array based on different levels, but so far I haven't had much success. Here are the data I'm working with (stored in a variable called totalByLevel): https://i.stack.imgur.com/rOP9 ...

Could there be a more sophisticated and streamlined method for verifying answers?

My app is designed for students and it automatically generates questions (such as distance/time questions), includes animations, and grades student answers. Here is a preview: https://i.sstatic.net/br8Ly.png Achieving my goal Although the code works fine ...

Tips for prohibiting the use of "any" in TypeScript declarations and interfaces

I've set the "noImplicitAny": true, flag in tsconfig.json and "@typescript-eslint/no-explicit-any": 2, for eslint, but they aren't catching instances like type BadInterface { property: any } Is there a way to configure tsco ...

Steps for specifying the required type of an Object literal in Typescript

Let's analyze a straightforward code snippet interface Foo{ bar:string; idx:number; } const test1:Foo={bar:'name'}; // this is highly recommended as it includes all required fields const test2={bar:'name'} as Foo; // this is ...

Discovering an array in Typescript containing a specific attribute, followed by embedding another array within that array

Below is an interface with a constant that inherits it: interface TreeNode { name: string; children?: TreeNode[]; } const TREE_DATA: TreeNode[] = [ { name: 'Mes services', }, { name: 'Logiciels externes' ...

Access file using operating system's pre-installed application

How can I open a file using the default application for that file type on different operating systems? For example, when opening an image.png on Mac, it should open with Preview, and on Windows with Windows Photo Viewer. I know you can use open image.png ...

Is it possible for me to make the default export anonymous?

Why bother naming the export if you already have a file with the default export name? This seems redundant and goes against the DRY principle. While there is a rule that discourages anonymous default exports, how can we enforce an error when someone does ...