Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doesn't support instanceof #90

Open
leosuncin opened this issue May 4, 2022 · 3 comments
Open

Doesn't support instanceof #90

leosuncin opened this issue May 4, 2022 · 3 comments

Comments

@leosuncin
Copy link

leosuncin commented May 4, 2022

The following test does'nt work because of instanceof operator

import { mock } from 'jest-mock-extended';
import { Repository } from 'typeorm';

import { Article } from '../entities/article.entity';

test('should be an instance of repository', () => {
  const mockRepository = mock<Repository<Article>>();

  expect(mockRepository instanceof Repository).toBe(true);

  mockRepository.findOne.mockResolvedValueOnce(new Article());

  await expect(mockRepository.findOne('a832e632-0335-4191-8469-4d849bbb72be')).resolves.toBeInstanceOf(Article);
})

I tried to create an instance of Repository and then override the properties with mock, it doesn't work

import { mock, MockProxy } from 'jest-mock-extended';
import { Repository } from 'typeorm';

import { Article } from '../entities/article.entity';

test('should be an instance of repository', () => {
  const mockRepository: MockProxy<Repository<Article>> = Object.assign(Object.create(Repository.prototype), mock<Repository<Article>>());

  expect(mockRepository instanceof Repository).toBe(true);

  mockRepository.findOne.mockResolvedValueOnce(new Article());

  await expect(mockRepository.findOne('a832e632-0335-4191-8469-4d849bbb72be')).resolves.toBeInstanceOf(Article);
})

The only way I found to "solved it" is defining the mocks manually

import { Repository } from 'typeorm';

import { Article } from '../entities/article.entity';

test('should be an instance of repository', async () => {
  const mockArticleRepository: jest.Mocked<Repository<Article>> = Object.assign(Object.create(Repository.prototype), {
    findOne: jest.fn(),
    // other methods or properties to mock
  });

  expect(mockRepository instanceof Repository).toBe(true);

  mockRepository.findOne.mockResolvedValueOnce(new Article());

  await expect(mockRepository.findOne('a832e632-0335-4191-8469-4d849bbb72be')).resolves.toBeInstanceOf(Article);
})

PS: there's another way but using jest-create-mock-instance#23

@j1mmie
Copy link

j1mmie commented Feb 24, 2023

Hello future Googlers - I am sending this PSA from 2023 to your time:

The same workaround linked in this issue (jest-create-mock-instance#23) can be applied to jest-mock-extended:

const webSocket = mock<ws.WebSocket>()
Object.setPrototypeOf(webSocket, ws.WebSocket.prototype)
expect(webSocket instanceof ws.WebSocket).toBe(true)

As a feature request, I would say this should not be the default behavior since it can have unexpected side effects. But if it were enabled by some argument to the mock function, that would be handy (and perhaps self-documenting). For example:

const webSocket = mock<ws.WebSocket>({}, {
  fakeInstanceOf: true       # Proposed, not yet implemented
})

@marchaos
Copy link
Owner

Would welcome a PR with the above proposal.

@koroldavid
Copy link

koroldavid commented Mar 17, 2023

@j1mmie, your answer helps a lot. Thanks!

However, after using Object.setPrototypeOf all mock's methods become undefined. Yet it will work fine if your class defines all methods as an arrow function property.

Also, using jest-create-mock-instance was not an ideal solution for me. It has a vice-versa issue of disability to mock arrow function properties (jest-create-mock-instance#34).

In the end, I come up with a temporary solution to use jest-mock-extended and jest-create-mock-instance together to compensate limitation:

import { createMockInstance } from 'jest-create-mock-instance';
import { mock } from 'jest-mock-extended';

type Constructable<T> = { new(...args: any[]): T } | { (...args: any[]): T };
type Class<T> = Constructable<T> & { prototype: T };

export function customMock<T>(instanceofClass: Class<T>) {
  const mockInstant = mock<T>();
  Object.setPrototypeOf(mockInstant, instanceofClass.prototype);

  const mockWithMethods = createMockInstance(instanceofClass);
  Object.assign(mockInstant, mockWithMethods);

  return mockInstant;
}

Also including tests, I required from customMock to pass:

class Dog {
  barks() { return 'bark'; }
  barksTwice = () => 'bark bark';
}

describe('customMock', () => {
  it('could pass instanceof check for mocked class type', () => {
    const dogMock = customMock(Dog);

    expect(dogMock instanceof Dog).toBeTruthy();
    expect(dogMock instanceof Error).toBeFalsy();
  });

  it('could substitute implementation of a function', () => {
    const dogMock = customMock(Dog);
    dogMock.barks.mockReturnValue('mock bark');

    expect(dogMock.barks()).toStrictEqual('mock bark');
  });

  it('could substitute implementation of an arrow function', () => {
    const dogMock = customMock(Dog);
    dogMock.barksTwice.mockReturnValue('mock bark bark');

    expect(dogMock.barksTwice()).toStrictEqual('mock bark bark');
  });
});

Beraliv pushed a commit to Beraliv/jest-mock-extended that referenced this issue May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants