Skip to content

[Feature]: Add utility for mocking a class #15723

@chiawendt

Description

@chiawendt

🚀 Feature Proposal

Add a utility jest.mockClass() for mocking a class.

Motivation

When testing classes in Jest, creating mocks is tedious and error-prone. You have to manually mock each method, which isn't type-safe and does not reflect source changes.

Pitch

Add a jest.mockClass() function that automatically creates a mock instance with all methods as jest.fn(). It should walk the prototype chain to include inherited methods.

Example

class SomeService {
  constructor(private logger: Logger) {}
  
  f() {
    this.logger.info("calling f");
  }
}

let someService: SomeService;
let logger: jest.Mocked<Logger>;

beforeEach(() => {
  // Current way (tedious)
  // This can expands to a hundred lines for a large test
  logger = {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  } as unknown as jest.Mocked<Logger>;

  // Proposed way (simple)
  logger = jest.mockClass(Logger);

  someService = new SomeService(logger);
});

test("should call logger.info", () => {
  someService.f();
  expect(logger.info).toHaveBeenCalled();
});

Reference implementation

/** create a mock instance with each method being jest.fn() */
export function mockClass<T>(ctr: new (...args: never[]) => T): jest.Mocked<T> {
  const instance: Record<string, jest.Mock> = {};
  let currentProto = ctr.prototype;
  while (currentProto && currentProto !== Object.prototype) {
    for (const key of Object.getOwnPropertyNames(currentProto)) {
      if (key === 'constructor') {
        continue;
      }

      // Avoid overriding a method from a child class with one from a parent.
      if (Object.prototype.hasOwnProperty.call(instance, key)) {
        continue;
      }

      const prop = (currentProto as Record<string, unknown>)[key];
      if (typeof prop === 'function') {
        instance[key] = jest.fn();
      }
    }
    currentProto = Object.getPrototypeOf(currentProto);
  }
  return instance as jest.Mocked<T>;
}

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