Simulate a complete class with its constructor, instance methods, and static functions

I have been searching for a comprehensive example that demonstrates how to properly mock all three elements (ClassA constructor, ClassA.func1 instance function, and ClassA.func2 static function) in my TypeScript project. In the code under test, I need to verify the mocked constructor, function, and static function using Jest for unit testing purposes.

While I typically use the mock factory approach for mocking Classes, I am facing difficulties in implementing it in this scenario. Below is an outline of the structure:

// ClassA.ts
export class ClassA {
  constructor() {
    // construction logic
  }

  public async func1(x: int): Promise<void> {
    // func1 logic
  }

  static func2(x: string | number): x is string {
    // func2 logic
    let conditionIsMet: boolean;

    // conditionIsMet logic

    if (conditionIsMet) {
      return true;
    }

    return false;
  }
}

// CodeUnderTest.ts
import { ClassA } from './ClassA';

export class ClassB {
  public async foo() {
    if (ClassA.func2('asdf')) {
      const a = new ClassA();
      await a.func1(45);
    }
  }
}

// UnitTest.ts
import { ClassB } from './ClassB';

// mock factory
const mockFunc1 = jest.fn();
const mockStaticFunc2 = jest.fn();
jest.mock('./ClassA', () => ({
  ClassA: jest.fn().mockImplementation(() => ({
    func1: mockFunc1,
    func2: mockStaticFunc2, 
  }),
});

beforeAll(() => {
  mockStaticFunc2.mockReturnValue(true);
});
describe('when ClassB.foo() is called', () => {
  describe('when ClassA.func2 is given a string parameter', () => {
    it('then ClassA.func2 is invoked with expected parameters, ClassA is constructed, ClassA.func1 is invoked with expected parameters', () => {
      // arrange
      const clsB = new ClassB();

      // execute
      clsB.foo();

      // expect
      expect(mockStaticFunc2).toHaveBeenCalledTimes(1);
      expect(mockStaticFunc2).toHaveBeenNthCalledWith(1, 'asdf');
      expect(ClassA).toHaveBeenCalledTimes(1);  
      expect(mockFunc1).toHaveBeenCalledTimes(1);
      expect(mockFunc1).toHaveBeenNthCalledWith(1, 45);
    });
  });
});

Could someone provide a detailed solution or example that outlines how to effectively mock all aspects (ClassA constructor, ClassA.func1 instance function, and ClassA.func2 static function) as described?

Answer №1

You may want to double-check your implementation of mocking the static method func2. Remember, static methods are accessed on the class itself. For more information, refer to this link: es6-class-mocks#automatic-mock

Here is an example that demonstrates how to mock static and instance methods:

ClassA.ts:

export class ClassA {
  constructor() {}
  public async func1(x: number): Promise<string> {
    return 'real value';
  }
  static func2(x: string | number) {
    return false;
  }
}

ClassB.ts:

import { ClassA } from './ClassA';

export class ClassB {
  public async foo() {
    if (ClassA.func2('asdf')) {
      const a = new ClassA();
      const r = await a.func1(45);
      console.log("🚀 ~ file: ClassB.ts:8 ~ ClassB ~ foo ~ r:", r)
    }
  }
}

ClassB.test.ts:

import { ClassB } from './ClassB';
import { ClassA } from './ClassA';

jest.mock('./ClassA');

describe('when ClassB.foo() is called', () => {
  describe('when ClassA.func2 is given a string parameter', () => {
    it('then ClassA.func2 is invoked with expected parameters, ClassA is constructed, ClassA.func1 is invoked with expected parameters', async () => {
      expect(jest.isMockFunction(ClassA)).toBe(true);
      expect(jest.isMockFunction(ClassA.func2)).toBe(true);
      expect(jest.isMockFunction(ClassA.prototype.func1)).toBe(true);
      const ClassAMocked = jest.mocked(ClassA);

      // arrange
      const clsB = new ClassB();
      jest.mocked(ClassA.func2).mockReturnValueOnce(true);
      jest.mocked(ClassA.prototype.func1).mockResolvedValueOnce('fake value');

      // execute
      await clsB.foo();

      // expect
      const classAInstance = ClassAMocked.mock.instances[0];
      expect(jest.isMockFunction(classAInstance.func1)).toBe(true);
      expect(ClassAMocked.func2).toHaveBeenNthCalledWith(1, 'asdf');
      expect(ClassAMocked).toHaveBeenCalledTimes(1);
      expect(classAInstance.func1).toHaveBeenNthCalledWith(1, 45);
    });
  });
});

Test result:

  console.log
    🚀 ~ file: ClassB.ts:8 ~ ClassB ~ foo ~ r: fake value

      at ClassB.log (stackoverflow/77559902/ClassB.ts:8:15)

 PASS  stackoverflow/77559902/ClassB.test.ts
  when ClassB.foo() is called
    when ClassA.func2 is given a string parameter
      ✓ then ClassA.func2 is invoked with expected parameters, ClassA is constructed, ClassA.func1 is invoked with expected parameters (11 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.508 s, estimated 1 s
Ran all test suites related to changed files.

package versions:

"jest": "^29.7.0"

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

Dragging and dropping elements onto the screen is causing them to overlap when trying

I am having trouble merging the drag and drop functionality from Angular Material with resizing in a table. Currently, both features are conflicting and overlapping each other. What I am hoping for is to automatically cancel resizing once drag and drop s ...

Referring to TypeScript modules

Consider this TypeScript code snippet: module animals { export class Animal { } } module display.animals { export class DisplayAnimal extends animals.Animal { } } The objective here is to create a subclass called DisplayAnimal in the dis ...

What is the reason behind the absence of compile time errors when using 'string' functions on an 'any' field type variable in TypeScript?

Looking at the following typescript code snippet: let a; a = "number"; let t = a.endsWith('r'); console.log(t); It is worth noting that since variable 'a' is not declared with a specific type, the compiler infers it as ...

The Strapi plugin seems to be encountering an issue as the API is not reachable, leading to a

In an attempt to create a custom API in Strapi backend, I developed a plugin called "test" for testing purposes. However, when trying to access the test response in Postman, it displays a 404 error stating that it is not accessible. Let me provide you wit ...

Manage thrown errors using http.post().subscribe()

There is a backend API for logging in with the possibility of returning a 401 Unauthorized error if the password provided is incorrect. I am wondering how to effectively manage and handle exceptions raised in Angular when interacting with this API. this.h ...

Mapping arguments as function values

Hello there, I have an array of objects that I am attempting to map through. const monthObject = { "March 2022": [ { "date": "2022-03-16", "amount": "-50", &q ...

Can anyone provide guidance on incorporating jQuery typing into an angular2 seed project?

I'm struggling to incorporate jQuery typings into my Angular2-seed project. Within my component, I am utilizing jQuery in the following manner: declare let $: any; export class LeafletComponent implements OnInit { ngOnInit(): void { th ...

Postpone the initial click action triggered by the specified directive

Is it possible to create a directive that prompts for confirmation when a button is clicked? This would involve storing the original event and only executing it once the user confirms their choice. A similar behavior has been mocked here: https://stackbl ...

Is it possible to efficiently utilize Map/Set in TypeScript/JavaScript when working with objects?

I'm currently transitioning from Java to TypeScript and I've encountered an issue with working with objects in hashmaps and hashsets. In Java, I would simply override the hashCode method to easily manipulate these data structures. Is there a simi ...

Issue found in the file assert.d.ts located in the node_modules directory: Expected '{' or ';' at line 3, character 68. Error code: TS1144

When attempting to start the angular application with ng serve, I encountered an error. Below are the project details: Angular CLI: 8.2.0 Node: 14.19.1 OS: darwin x64 Angular: 8.2.0 ... animations, cli, common, compiler, compiler-cli, core, forms ... platf ...

Troubleshooting the error message "TypeError: Cannot read property 'name' of undefined" when working with data binding in Angular 4

I am brand new to Angular and I have been working on creating a custom Component. Specifically, I am trying to display a list of Courses (objects) which consist of two properties: id and name. So far, this logic is functioning properly. However, when attem ...

The object returned by bodyParser is not a string but a data structure

I have been working on implementing a JSON content listener in Typescript using Express and Body-parser. Everything is functioning perfectly, except when I receive the JSON webhook, it is logged as an object instead of a string. import express from 'e ...

Angular Firebase Email Verification sent to an alternate email address

I am facing a challenge with my website that only accepts emails from a specific domain, such as a university domain. Users who want to sign up using Facebook or Google need to verify their university email, but I am struggling to find a way to send the ve ...

Issue with jsPDF: PNG file is either incomplete or corrupted

I'm encountering an issue while attempting to pass Image data to the addImage function. I have tried downgrading the versions of jspdf and html2canvas, as well as experimenting with different ways to import the two libraries, but the problem still per ...

Can you explain the significance of the colon in this context?

Upon reviewing some SearchKit code snippets (composed with react/jsx and es2015), I came across the following line in a jsx file: const source:any = _.extend({}, result._source, result.highlight) I am curious about the purpose or significance of the colo ...

How to modify the background color within the mat-menu-panel

Incorporating angular 9 and less into my current project, I have encountered an issue with a mat-menu-panel where my mat-menu-item is located. While I have successfully changed the color of my mat-menu-item, I am now faced with the challenge of changing th ...

Retrieve the text content from the HTML document

I'm facing a beginner's challenge. I have a div element and I want to extract the URL from the data-element attribute into a .json file Is there a way to do this? <div content="" id="preview" data-element="http://thereislink" class="sample ...

Error is being thrown due to defining a variable after it has already been declared and

Before I use a variable, I encountered the issue of using it before its definition, interface IProps extends WithStyles<typeof STYLES>; const STYLES = () => ({ }) Although it didn't cause any errors, a warning appeared: STYLES used befo ...

Using a function as an argument to handle the onClick event

I have a function that generates a React.ReactElement object. I need to provide this function with another function that will be triggered by an onClick event on a button. This is how I call the main function: this._createInjurySection1Drawer([{innerDra ...

The combination of TypeScript decorators and Reflect metadata is a powerful tool for

Utilizing the property decorator Field, which adds its key to a fields Reflect metadata property: export function Field(): PropertyDecorator { return (target, key) => { const fields = Reflect.getMetadata('fields', target) || []; ...