Skip to content

[Feature]: Add ability for presets interfere with jest auto mock #15429

Closed
@MillerSvt

Description

@MillerSvt

🚀 Feature Proposal

I suggest adding the ability to interfere with the jest auto mock logic in order to be able to modify its behavior at the presets level.

The original problem is that when you try to mock the angular library, then when creating the service, undefined is returned. This is because Angular creates services using ɵfac and ɵprov, which are replaced with jest.fn(), which by default return undefined.

Related to: thymikee/jest-preset-angular#2908

Motivation

Currently, I'm trying to test a component that depends on an external Angular library. This library provides a SomeService that is provided in root.

Here is my component:

class Component {
  someService = inject(SomeService);

  method() {
    this.someService.someMethod();
  }
}

Here is my test:

import { SomeService } from 'some-library';

jest.mock('some-library');

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [SomeService],
  });

  fixture = TestBed.createComponent(Component);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

it('test', () => {
  component.method();
});

Currently I receive error: Cannot read property 'someMethod' of undefined.

Research

I've done some research. Here is my working patch:

jest.mock(`some-library`, () => {
    const moduleMock = jest.createMockFromModule(`some-library`);

    function* walk(obj: unknown, walkedNodes: any[] = []): Generator<[key: string, target: any]> {
        if ((typeof obj !== `function` && typeof obj !== `object`) || walkedNodes.includes(obj)) {
            return;
        }

        for (const key in obj) {
            if (typeof key === `string` && key.startsWith(`ɵ`)) {
                yield [key, obj];
            }

            yield* walk(obj[key], [...walkedNodes, obj]);
        }
    }

    for (const [key, target] of walk(moduleMock)) {
        switch (key) {
            case `ɵfac`: {
                target[key] = () => new target();
                break;
            }
            case `ɵprov`: {
                if (target[key] === undefined) {
                    break;
                }

                if (`factory` in target[key]) {
                    target[key].factory = () => new target();
                }

                break;
            }
        }
    }

    return moduleMock;
});

However, it would not be beneficial to repeat it in each test and mock.

Example of proposed API

// setup-jest.ts

jest.onGenerateMock((moduleName, moduleMock) => {
  // some modifications for moduleMock
  return moduleMock;
});

This hook called only if mock was generated by _generateMock

Pitch

I believe that this can make it easier to work with jest auto mock in projects that often use special tools.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions