Giter Site home page Giter Site logo

marchaos / jest-mock-extended Goto Github PK

View Code? Open in Web Editor NEW
808.0 5.0 55.0 559 KB

Type safe mocking extensions for Jest https://www.npmjs.com/package/jest-mock-extended

License: MIT License

TypeScript 99.67% JavaScript 0.33%
jest typescript mocking-framework mock calledwith

jest-mock-extended's Introduction

jest-mock-extended

Type safe mocking extensions for Jest ๐Ÿƒ

Build Status Coverage Status npm version License: MIT npm downloads

Features

  • Provides complete Typescript type safety for interfaces, argument types and return types
  • Ability to mock any interface or object
  • calledWith() extension to provide argument specific expectations, which works for objects and functions.
  • Extensive Matcher API compatible with Jasmine matchers
  • Supports mocking deep objects / class instances.
  • Familiar Jest like API

Installation

npm install jest-mock-extended --save-dev

or

yarn add jest-mock-extended --dev

Example

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

interface PartyProvider {
   getPartyType: () => string;
   getSongs: (type: string) => string[]
   start: (type: string) => void;
}

describe('Party Tests', () => {
   test('Mock out an interface', () => {
       const mock = mock<PartyProvider>();
       mock.start('disco party');
       
       expect(mock.start).toHaveBeenCalledWith('disco party');
   });
   
   
   test('mock out a return type', () => {
       const mock = mock<PartyProvider>();
       mock.getPartyType.mockReturnValue('west coast party');
       
       expect(mock.getPartyType()).toBe('west coast party');
   });

    test('throwing an error if we forget to specify the return value')
        const mock = mock<PartyProvider>(
            {},
            {
                fallbackMockImplementation: () => {
                    throw new Error('not mocked');
                },
            }
        );

        expect(() => mock.getPartyType()).toThrowError('not mocked');
    });

Assigning Mocks with a Type

If you wish to assign a mock to a variable that requires a type in your test, then you should use the MockProxy<> type given that this will provide the apis for calledWith() and other built-in jest types for providing test functionality.

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

describe('test', () => {
    let myMock: MockProxy<MyInterface>;

    beforeEach(() => {
        myMock = mock<MyInterface>();
    })

    test(() => {
         myMock.calledWith(1).mockReturnValue(2);
         ...
    })
});

calledWith() Extension

jest-mock-extended allows for invocation matching expectations. Types of arguments, even when using matchers are type checked.

const provider = mock<PartyProvider>();
provider.getSongs.calledWith('disco party').mockReturnValue(['Dance the night away', 'Stayin Alive']);
expect(provider.getSongs('disco party')).toEqual(['Dance the night away', 'Stayin Alive']);

// Matchers
provider.getSongs.calledWith(any()).mockReturnValue(['Saw her standing there']);
provider.getSongs.calledWith(anyString()).mockReturnValue(['Saw her standing there']);

You can also use mockFn() to create a jest.fn() with the calledWith extension:

 type MyFn = (x: number, y: number) => Promise<string>;
 const fn = mockFn<MyFn>();
 fn.calledWith(1, 2).mockReturnValue('str');

Clearing / Resetting Mocks

jest-mock-extended exposes a mockClear and mockReset for resetting or clearing mocks with the same functionality as jest.fn().

import { mock, mockClear, mockReset } from 'jest-mock-extended';

describe('test', () => {
   const mock: UserService = mock<UserService>();
   
   beforeEach(() => {
      mockReset(mock); // or mockClear(mock)
   });
   ...
})

Deep mocks

If your class has objects returns from methods that you would also like to mock, you can use mockDeep in replacement for mock.

import { mockDeep } from 'jest-mock-extended';

const mockObj: DeepMockProxy<Test1> = mockDeep<Test1>();
mockObj.deepProp.getNumber.calledWith(1).mockReturnValue(4);
expect(mockObj.deepProp.getNumber(1)).toBe(4);

if you also need support for properties on functions, you can pass in an option to enable this

import { mockDeep } from 'jest-mock-extended';

const mockObj: DeepMockProxy<Test1> = mockDeep<Test1>({ funcPropSupport: true });
mockObj.deepProp.calledWith(1).mockReturnValue(3)
mockObj.deepProp.getNumber.calledWith(1).mockReturnValue(4);

expect(mockObj.deepProp(1)).toBe(3);
expect(mockObj.deepProp.getNumber(1)).toBe(4);

Can can provide a fallback mock implementation used if you do not define a return value using calledWith.

import { mockDeep } from 'jest-mock-extended';
const mockObj = mockDeep<Test1>({
    fallbackMockImplementation: () => {
        throw new Error('please add expected return value using calledWith');
    },
});
expect(() => mockObj.getNumber()).toThrowError('not mocked');

Available Matchers

Matcher Description
any() Matches any arg of any type.
anyBoolean() Matches any boolean (true or false)
anyString() Matches any string including empty string
anyNumber() Matches any number that is not NaN
anyFunction() Matches any function
anyObject() Matches any object (typeof m === 'object') and is not null
anyArray() Matches any array
anyMap() Matches any Map
anySet() Matches any Set
isA(class) e.g isA(DiscoPartyProvider)
includes('value') Checks if value is in the argument array
containsKey('key') Checks if the key exists in the object
containsValue('value') Checks if the value exists in an object
has('value') checks if the value exists in a Set
notNull() value !== null
notUndefined() value !== undefined
notEmpty() value !== undefined && value !== null && value !== ''
captor() Used to capture an arg - alternative to mock.calls[0][0]

Writing a Custom Matcher

Custom matchers can be written using a MatcherCreator

import { MatcherCreator, Matcher } from 'jest-mock-extended';

// expectedValue is optional
export const myMatcher: MatcherCreator<MyType> = (expectedValue) => new Matcher((actualValue) => {
    return (expectedValue === actualValue && actualValue.isSpecial);
});

By default, the expected value and actual value are the same type. In the case where you need to type the expected value differently than the actual value, you can use the optional 2 generic parameter:

import { MatcherCreator, Matcher } from 'jest-mock-extended';

// expectedValue is optional
export const myMatcher: MatcherCreator<string[], string> = (expectedValue) => new Matcher((actualValue) => {
    return (actualValue.includes(expectedValue));
});

jest-mock-extended's People

Contributors

bbarry avatar beraliv avatar brutyler avatar codeinabox avatar dependabot[bot] avatar dlech avatar effs avatar franky47 avatar hantsy avatar hnisiji avatar iisergey avatar jeremyloy avatar laurence-hudson-mindfoundry avatar marchaos avatar millsp avatar moltar avatar msftenhanceprovenance avatar nlkluth avatar regevbr avatar s-montigny-desautels avatar shinglyu avatar skovhus avatar sytten avatar timdavish avatar toilal avatar vdhpieter avatar vinh0604 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

jest-mock-extended's Issues

Mocha

Would this work in mocha?

Bug: mocking Date with calling date.getHours() causes Error

I've got a bug:

interface d {
date: Date
}
let m = mock ({
date: new Date()
})

m.date.getHours() //leads to TypeError: this is not a Date object at Proxy.getHours ()

Reason:
Current get method of Proxy's handler loses its context:
return obj[property]

Solving:
Bind a context for the functions:
return obj[property].bind(obj)

As a more specific solving: bind context only for Date's function

Angular Services cannot be mocked in Angular TestBed

If I mock an Angular Injectable Service, I get the following as soon as I include it in the module providers list:

TypeError: function is not iterable (cannot read property Symbol(Symbol.iterator))
    at new Set (<anonymous>)

Pseudo code (edited for simplicity)

const issueServiceMock = mock<IssueService>();
TestBed.configureTestingModule({
  imports: [ReactiveFormsModule, RouterTestingModule],
  declarations: [
    IssueDashboardContainerComponent
  ],
  providers: [
    {provide: IssueService, useValue: issueServiceMock},
  ]
});
fixture = TestBed.createComponent(IssueDashboardContainerComponent);  // CRASHES HERE

typescript 3.9.4 compilation issues

After upgrading to typescript 3.94 I started getting the following errors:

 error TS2590: Expression produces a union type that is too complex to represent.

The issue doesn't happen for ts 3.8

To reproduce, install the latest typeorm package and typescript 3.9.4 and run:

import { Connection } from 'typeorm';
import * as typeorm from 'typeorm';
import { mock } from 'jest-mock-extended';

const typeOrmMock = mock<typeof typeorm>();
const connectionMock = mockDeep<Connection>();
typeOrmMock.createConnection.mockResolvedValue(connectionMock); //  error TS2590: Expression produces a union type that is too complex to represent.

mocking promises?

so I was going to use a deep mock for this, but didn't find a way to do it, is there a better way? if not could there be a better way?

    const axios: MockProxy<AxiosInstance> = mock<AxiosInstance>();
    const response: MockProxy<AxiosResponse> = mock<AxiosResponse>();
    response.data = {
      result: user,
    };
    axios.get.mockResolvedValue(response);

Matcher to match object by values instead of reference

First of all: thank you so much for this package. It's super helpful and makes testing in TS finally fun. ๐Ÿ™๐Ÿป

I had to test the following code:

class SubjectUnderTest
{
    private someService: ServiceA;

   constructor(someService: ServiceA) {
       this.someService = someService;
   }

    public someMethod(object args): void {
        this.someService.do({id: 'foo', ...args})
    }
}


// Test
describe('test subjextUnderTest', () => {
   it('test', async () => {
       const args = {foo: 'bar'};

       const mockServiceA = mock<ServiceA>();
       mockServiceA.do.calledWith({id: 'foo', ...args})
   });
});

The problem is that the both objects don't share the same reference (it seems if you pass in arguments to calledWith() they are matched by reference) and I had to write a custom matcher to match the object's properties.

const myMatcher: MatcherCreator<object> = (expectedValue) => new Matcher((actualValue) => {
    for (const key in expectedValue) {
        if (expectedValue[key] !== actualValue[key]) {
            return false;
        }
    }
    return true;
});

What do you think if we supply such a matcher (maybe add support for nested objects) with this package? I assume this is a quite generic matcher that can be of use for many people using this package.

An improved version could be a general equals matcher that compares values with === objects by iterating over them and comparing the values with ===:

import { Matcher, MatcherCreator } from 'jest-mock-extended';

export const equals: MatcherCreator<any> = expectedValue => new Matcher((actualValue) => {
    return compareValue(expectedValue, actualValue);
});

function compareValue(expectedValue: any, actualValue: any): boolean 
{
    if (typeof actualValue !== typeof expectedValue) {
        return false;
    }

    if ('object' === typeof actualValue) {
        return compareObject(expectedValue, actualValue); 
    }

    return expectedValue === actualValue;
}

function compareObject(expectedValue: object, actualValue: object): boolean
{
    for (const key in expectedValue) {
        if (false === compareValue(expectedValue[key], actualValue[key])) {
            return false;
        }
    }

    return true;
}

it will be cool to have an equality matcher

Instead of creating a custom matcher, it will be cool to include one built in.
For example:

import { Matcher } from 'jest-mock-extended';
import { equals } from 'expect/build/jasmineUtils';

export const isEqual = <T>(expectedValue: T) =>
  new Matcher<T>((actualValue: T) => {
    return equals(actualValue, expectedValue);
  });

How to clear or reset all the mocked functions?

Firstly, thanks for creating this, it's brilliant.

Before each of my tests I want to clear out my mocks but I can't find an easy way to do this. I could go function by function but this is messy when mocking a large interface. I know I could also just re-instantiate the mocked interface but that feels wrong. I imagine it takes time compared to just doing a mockReset() or mockClear() on each function.

Ideally, I'd like this, or similar.

    const mockUserService: UserService = mock<UserService>();
    let userController: UserController;

    beforeEach(() => {
        mockUserService.mock.mockReset();
        userController = new UserController(mockUserService);
    });

Using calledWith where a parameter in an object is a callback

I have the following code in my application

public get<T>(
      url: string,
      params: AxiosRequestConfig
  ): Promise<AxiosResponse<T>> {
  return this.axiosInstance.get<T>(url, params);
}

where params is something like

{
  baseURL: 'example.com',
  headers: {
    'X-Custom-Header': '...',      
  },
  transformResponse: (data) => {
     return JSON.parse(...callbackfunction(data));
  }

When I mock this using the following code

mockAxiosInstance
    .get
    .calledWith(
        'any',
        objectContainsKey('baseURL') &&
        objectContainsValue('example.com') &
        objectContainsKey('headers') &&
        objectContainsValue({'X-Session-Key'
        objectContainsKey('transformResponse
    )
    .mockReturnValue(new Promise(() => {}));

This works without any problems, however, as you can see I also have a callback function as an option. How would I mock that?

Thank you :) Your library helped me so far.

`mockResolvedValue` Type Error

First, I'm using prisma as Type ORM client with apollo-server-express. My use case is context mock like below

context/contextMock.ts

import { MockProxy, mockDeep } from "jest-mock-extended";
import { Request, Response } from "express";

export type MockContext = {
  request: MockProxy<Request>;
  response: MockProxy<Response>;
  ...
}

export const createMockContext = (): MockContext => {
  return {
    request: mockDeep<Request>(),
    response: mockDeep<Response>(),
    ...
  }
}

myErrorTestCode.spec.ts

import { MockContext, Context, createMockContext } from "~/context/contextMock";
import { myRequest, myController } from "..";
import expressRequestMock from "express-request-mock";

let mockCtx: MockContext;
let ctx: Context;
let myController: MyController;

describe("myRequest", () => {
  beforeEach(() => {
    mockCtx = createMockContext();
    ctx = mockCtx as unknown as Context; // Needed until https://github.com/marchaos/jest-mock-extended/issues/25
    myController = new myRequest(ctx.prisma)
      .myController;
  });
  it("should return isExist is false when token is not exist", async () => {
    mockCtx.prisma.myToken.findFirst.mockResolvedValue(null); // <-- Type Error
    const resJson = {
      status: 400,
      errorCode: 10021,
      message: "Invalid token",
      schema,
      version,
    };
    const { res } = await expressRequestMock(myController);
    const data = JSON.parse(res._getData());
    expect(res.statusCode).toBe(400);
    expect(data).toEqual(resJson);
  });
}

It was fine [email protected]. after upgrate to [email protected] report type error not having mockResolvedValue

How can I change use case in version 2?

Thank you.

Add generics to mockReturnValue and mockResolvedValue

It would be good to be able to override the return types via generic param to the mockReturnValue and mockResolvedValue.

The use case is that I am mocking a third party library (aws-sdk), which has a very complicated return type, and I don't actually care about all of the meta return values. I only need to mock the value that I care about in my application.

Thanks.

Mock as iterable object

Hello
I had problem with expect(mock).toEqual(mock), because in a method toEqual be call iterator for the mock and own mock return not correct data for an iterator. You can see more information in https://medium.com/front-end-weekly/thank-u-symbol-iterator-next-aef9f09ff78. I used expect(mock.property).toEqual(mock.property)

interface SomeObject {
  name: string;
}
it('should work', async () => {
  const mockObject= mock<SomeObject>();
  mockObject.name = 'mock';
  expect(mockObject.name ).toEqual(name ); // that work correct
  expect(mockObject).toEqual(mockObject); // Error
});

Example Error:

TypeError: Cannot read property 'next' of undefined

    at Object.test (src\Mock.spec.ts:271:37)
    at Object.asyncJestTest (node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:102:37)
    at resolve (node_modules\jest-jasmine2\build\queueRunner.js:43:12)
    at new Promise (<anonymous>)
    at mapper (node_modules\jest-jasmine2\build\queueRunner.js:26:19)
    at promise.then (node_modules\jest-jasmine2\build\queueRunner.js:73:41)
    at process.internalTickCallback (internal/process/next_tick.js:77:7)

Change name of calledWith method

Hi,

I love this library and use it for all my tests. However, one thing has caught me and my colleagues out several times. It hinges around the calledWith matcher.

In the following code:

      mockServiceApi.updateService.calledWith(4, 2).mockResolvedValue(42);

We all continually mistake this to mean "expect this method to be called with parameters 4 and 2 and, if so, return a promise to 42".

We therefore continually think that this should fail with an error if the updateService method is called with some other parameters. This is because we are used to jest code where we say things like:

      expect(mockServiceApi.updateService).toHaveBeenCalledWith(4, 2);

which would throw an error if it wasn't called with those parameters.

I believe calledWith is actually saying "when this method is called with parameters 4 and 2 then return a promise to 42".

So, if it is called with some other parameters then it will not do the action of returning a promise to 42.

I think it would help if this was renamed to whenCalledWith. The code would then read:

      mockServiceApi.updateService.whenCalledWith(4, 2).mockResolvedValue(42);

This would make it explicit that its not an expectation that will throw. You could just introduce a new matcher named whenCalledWith that just forwards on to calledWith to keep your library backwards compatible.

What do you think?

Maintainer help?

@marchaos Do you need help with maintenance? If you setup CICD for npm publish, I would be willing to help merge the PR and release version.

ESM compabillity

Since 1.0.17 or 1.0.18 this package no longer works with jest in esm mode.

image

I think it's because of the new different typescript compilation.

Order of calling mock.calledWith() matters

Unless I'm missing something, which is quite possible. The order in which you set up mocks with calledWith matters.

A simple example:

describe('A simple example', () => {
  let mockHttpClient: MockProxy<HttpClient>;

  beforeEach(() => {
    mockHttpClient = mock<HttpClient>();
  });

  it('will return the most generic mock value if it\'s defined first', async () => {
    mockHttpClient.get.calledWith(any()).mockResolvedValue('first');
    mockHttpClient.get.calledWith(anyString()).mockResolvedValue('second');
    mockHttpClient.get.calledWith('bar').mockResolvedValue('third');


    await expect(mockHttpClient.get('foo')).resolves.toBe('second');
  });
});

in the example above the mock returns 'first'. In this example it isn't that big of a deal, you could just reorder them and put the more specific ones ontop and the mock works as expected.

However, the use case where I think it becomes problematic is if you want to define the generic mockResolvedValue in a beforeEach for all tests, and then define more specific mockResolvedValues with in a specific test, this behavior will cause an issue in that scenario.

Here's an example of the more problematic use case for reference

describe('A simple example', () => {
  let mockHttpClient: MockProxy<HttpClient>;

  beforeEach(() => {
    mockHttpClient = mock<HttpClient>();
    mockHttpClient.get.calledWith(any()).mockResolvedValue('first');
  });

  it('will return the most generic mock value if it\'s defined first', async () => {
    mockHttpClient.get.calledWith(anyString()).mockResolvedValue('second');
    mockHttpClient.get.calledWith('bar').mockResolvedValue('third');
    await expect(mockHttpClient.get('foo')).resolves.toBe('second');
  });
});

Any insight or suggestions is appreciated. Also, love this library. It's super useful.

.then defined as a property on an object cannot be used for mock implementations

This breaks older APIs that might chain a .then() call, like the following example using jsforce:

Minimum test setup example:

const batchMock = mock<jsforce.Batch>();
batchMock.then.mockResolvedValue(123)
// TypeError: Cannot read property 'mockResolvedValue' of undefined

Batch source code with .then defined on Batch prototype: https://jsforce.github.io/jsforce/doc/api_bulk.js.html#line486

The reason is the following line:

if (property === 'then') {

calledWith return value redefinition dont work

Hi!

I use jest-mock-extended v1.0.10 + jest v24.9.0
Failing to change return value of calledWith for the same argument. Here is a minimal example:

import { mockDeep } from 'jest-mock-extended';

interface A {
  f: (s: string) => string;
}

const m = mockDeep<A>();
const value = 'value';
m.f.calledWith(value).mockReturnValue('result 1');
m.f.calledWith(value).mockReturnValue('result 2');

test('', () => {
  expect(m.f(value)).toBe('result 2');
});

Test fails, m.f(value) returns 'result 1'.
Is this a bug?

boolean field mock changes to function on access

After defining a mock for a class with values

const mock = mock<MyClass>({ b: false, s: 'someText' }); // option 1
...
const mock = mock<MyClass>(); // option 2
mock.b = false

and accessing it with mock.b the field changes to a function mockConstructor () ....
What's even more weird is that mock.s returns someText as expected

Cannot pass mock as parameter for toBeCalledWith

When I call toBeCalledWith passing another mock as parameter an error occurs.

Sample code:

expect(myMock.sampleMethod).toBeCalledWith(otherMock);

Error:

TypeError: Result of the Symbol.iterator method is not an object

Inability to use mocks with rxjs

Hello,

Very nice library, which works well in a lot of cases, except..

Most of my code involves RxJS observables and I can't do something as simple as

const x = mock<T>();
await lastValueFrom(of(x));

It ends with a timeout due to the implementation of of in RxJS.

It seems to boil down to RxJS looking for a schedule property/method on the object passed to of(), and when that property is present RxJS goes down the "wrong" path thinking a scheduler was passed.

There is already a quick fix for promises in the proxy used by jest-mock-extended: https://github.com/marchaos/jest-mock-extended/blob/master/src/Mock.ts#L91

I don't know if it's feasible to add a similar condition for schedule, as it might be a legit property/method in code tested by current users...

Empty array in mock

This test fails with "RangeError: Invalid array length"

interface Test {
    arr: String[]
}

it('tests', () => {
    let m = mock<Test>({ arr: [] });

    expect(m.arr).toHaveLength(0);
})

Unable to mock getter

Is it possible to mock getters?
I always get the error Cannot assign to 'xyz' because it is a read-only property

Test hangs when async method returns a mock

Hello there, I've found a problem that is keeping me from using this very helpful package.

I have a use case very similar to the structure of this example code, where my test subject receives an async factory as dependency which is called inside of it to do stuff:

it('should work', async () => {
  interface Engine {
    start: () => Promise<void>;
  }

  const makeCar = async ({ makeEngine }) => {
    const engine = await makeEngine(); // code hangs here
    return {
      turnOn: async () => {
        await engine.start();
        return true;
      },
    };
  };

  const engineMock = mock<Engine>();
  const car = await makeCar({ makeEngine: async () => engineMock });
  await car.turnOn();
  expect(engineMock.start).toHaveBeenCalled();
});

The problem here is that the code hangs on await makeEngine() for some reason.

If I change the factory to not be async like this const car = await makeCar({ makeEngine: () => engineMock }); it will work perfectly.
Also if I change engineMock for a simple mock object like { start: jest.fn() } it will also work, which leads me to think that the problem is in this library.

Do you have any idea on what could be causing this issue?

Thanks!

Mocking objects with non-function values

Hi! Is it possible to mock simple objects somehow based on interfaces where the value is not a function?

Instead

interface PartyProvider {
   getPartyType: () => string;
   getSongs: (type: string) => string[];
}

I'd like to mock

interface PartyProvider {
   getPartyType: string;
   getSongs: string[];
}

(and also being able to do that in nested form).

Problem mocking interface that has 'name' property.

Hi,
I use jest-mock-extended 1.0.13. Can not setup property 'name' of the mock. The following test

interface A {
  name: string;
}

interface B {
  a: A;
}

test('', () => {
  const bMock = mockDeep<B>();
  bMock.a.name = '';
});

ends up with

TypeError: Cannot assign to read only property 'name' of function 'function mockConstructor() {
    return fn.apply(this, arguments);
}'

Yarn v2 support

I get the following error when trying to install jest-mock-extended when using Yarn v2

jest-mock-extended@npm:1.0.8 [f88a9] doesn't provide typescript@>=3.7.0 requested by ts-essentials@npm:4.0.0

Can you add typescript as a peerDependency? Here's the Yarn advice.

mockReset doesn't work well with calledWith

When performing mockReset, the jest.fn() method is being reset which is great, but the inner state of the calledWith scope is maintained, thus the jest.fn() is not called again with mockImplementation and the call stack just gets piled up

Mock array inside an interface returns Proxy

Hi, I want to mock this interface:

export interface FileRequest {
    readonly folder: string;
    readonly params: any[];
}

I do something like this:

const mockFileRequest = mock<FileRequest>({
        _id: FileRequestHelper.generateObjectID(),
        folder: 'mock-folder',
        params: [
          {
            param1: {
              param11: 'value11'
            }
          },
          {
            param2: {
              param21: 'value21'
            }
          }
        ]
      });

And when I want to see its content, I realize that params property has the type Proxy.

Any solution?

Mocking boolean

class Test {
    value: number;
    flag: boolean;
}

it('tests', () => {
    var t = mock<Test>({value: 123, flag: false});

    expect(t.value).toBe(123);
    expect(t.flag).toBeFalsy();
});

Running the above test fails with:

expect(received).toBeFalsy()
Received: [Function mockConstructor]

Edit: Noticed that this is a duplicate of #30, for which a PR exists. Please merge it.

MockImplementations are mocked wihout using deepMock

Hi!

When passing a mock implementations to mock, every sub object get wrapped in a Proxy. For example,

class ObjectWithMap {
  map = new Map();
}

const test = mock({ objectWithMap: new ObjectWithMap() });
console.log(util.types.isProxy(test)); // -> true
console.log(util.types.isProxy(test.objectWithMap)); // -> true
console.log(util.types.isProxy(test.objectWithMap.map)); // -> true

Which I think is quite unexpected. I believe it should only happen when using the deepMock function.
Also, it break some built-in object (Map, Set, Date, etc) as seen in #59.

Is it the expected behavior?

mockClear() is not removing calledWith() from mocks

Hi!

If I use mockClear() in combination with calledWith() it is still using the previous mocks:

import { mock, mockClear } from 'jest-mock-extended';

class Implementation {
  public doSomething(arg: string): string {
    return arg;
  }
}

describe('mockClear should clear calledWith', () => {
  let mockedClass = mock<Implementation>();
  afterEach(() => {
    mockClear(mockedClass);
  });

  it('should do sth', () => {
    mockedClass.doSomething.calledWith('hello').mockReturnValue('firstMock');
    expect(mockedClass.doSomething('hello')).toEqual('firstMock');
  });

  it('should do sth2', () => {
    mockedClass.doSomething.calledWith('hello').mockReturnValue('secondMock');
    expect(mockedClass.doSomething('hello')).toEqual('secondMock');
    // fails with:
    // Error: expect(received).toEqual(expected) // deep equality

    // Expected: "secondMock"
    // Received: "firstMock"
  });
});

Workaround for now is to re-create the mock instead of calling mockClear()

deep mocks to deeply mock functions

Currently, when creating a deep mock, there is no way to control the mocks of return values of first level mocked functions.

Essentially I want to be able to do:

interface A {
  y: () => Promise<string>;
}

interface B {
  x: () => Promise<A>;
}

const mocked = mockDeep<B>();
mocked.x.y.mockResolvedValue(`str`);

Private properties prevent casting

This is a complex problem. I am a mocking a class from the Prisma library that looks like

export declare class PrismaClient<T extends PrismaClientOptions = {}, U = keyof T extends 'log' ? T['log'] extends Array<LogLevel | LogDefinition> ? GetEvents<T['log']> : never : never> {
  private fetcher;
  private readonly dmmf;
  private connectionPromise?;
  private disconnectionPromise?;
  private readonly engineConfig;
  private readonly measurePerformance;
  private engine: Engine;
  private errorFormat: ErrorFormat;
  get event(): EventDelegate;
}

I store an instance of this class in a context type:

export type Context = {
  prisma: PrismaClient;
};

For my tests, I created a mock type for the context:

export type MockContext = {
  prisma: MockProxy<PrismaClient>;
};

When I want to pass this mock context to the function to test, it is unable to cast the type to a Context because of the private properties:

Conversion of type 'MockContext' to type 'Context' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Types of property 'prisma' are incompatible.
    Type 'MockProxy<PrismaClient<{}, never>>' is missing the following properties from type 'PrismaClient<{}, never>': fetcher, dmmf, engineConfig, measurePerformance, and 3 more.

So for now I did a hack where I store a casted context in my mocked context:

export type MockContext = {
  prisma: MockProxy<PrismaClient>;
  ctx: Context;
};

export const createMockContext = (): MockContext => {
  const baseCtx = {
    prisma: mockDeep<PrismaClient>(),
  };

  const mockCtx = {
    ...baseCtx,
    ctx: baseCtx as Context,
  };

  return mockCtx;
};

But it would be great if I could just cast the mock context directly.
Thanks a lot!

Provide mock implementations

Instead of having to modify mock implementations manually, it might be nice to allow an argument as the first argument in mock<...>({ ... }) which defines mock implementations/values for the instance:

interface ResourcesApi {
    get(id: string): Promise<any>
}

interface Api {
    resources: ResourcesApi
}

const api = mock<Api>({
  resources: {
    get: true // equivalent to api.resources.get.mockReturnValue(true)
    // or get: () => true which is equivalent to api.resources.get.mockImplementation(() => true)
  }
})

// instead of ...
// 

Refer to #3 for the nested support.

Thoughts?

Handle nested mocked values

As the title says, when there are nested callable values, I would expect those to be typed as mocked values too.

interface ResourcesApi {
    get(id: string): Promise<any>
}

interface Api {
    resources: ResourcesApi
}

const api = mock<Api>()

// The next line will get a type error because `get` isn't mocked.
api.resources.get.mockReturnValue(true)

Thoughts?

Date proxy in 2.0.4 breaks comparison

I have a test like this that is now breaking because obj.createdAt is a Proxy, not a Date after upgrading to 2.0.4. (excuse the indentation, i omitted some irrelevant context)

class MyModel extends Model {
  id!: string
  otherField!: string
  containerId!: string
  createdAt!: Date
  deletedAt?: Date

  findOne = jest.fn()
  findAll = jest.fn()
}

describe('Paginator', () => {
  const paginator = new Paginator()

  beforeEach(() => {
    jest.resetAllMocks()
  })

    it('when DESC returns less-than where clause', async () => {
      const obj = mock<MyModel>({ id: 'mem_123', createdAt: new Date('2021-05-21T16:12') })
      MyModel.findOne = jest.fn().mockReturnValueOnce(obj)

      const whereOptions = await paginator._getPaginationWhereOptions(
        MyModel,
        { otherField: 'banana' },
        { limit: 10, cursorObjectId: 'mem_123', cursorDirection: 'forwards' },
        'createdAt',
        'DESC',
      )

      expect(whereOptions).toStrictEqual({
        createdAt: {
          [Op.lt]: obj.createdAt,
        },
      })
  })
})

The failure reported is

FAIL ../../src/core/services/__tests__/Paginator.test.ts
  โ— Paginator โ€บ _getPaginationOptions โ€บ when DESC returns less-than where clause

    expect(received).toStrictEqual(expected) // deep equality

    Expected: {"createdAt": {Symbol(lt): "2021-05-21T16:12:00.000Z"}}
    Received: serializes to the same string

      66 |       )
      67 |
    > 68 |       expect(whereOptions).toStrictEqual({
         |                            ^
      69 |         createdAt: {
      70 |           [Op.lt]: obj.createdAt,
      71 |         },

      at Object.<anonymous> (../../src/core/services/__tests__/Paginator.test.ts:68:28)

Workaround: I can get this test to pass by declaring obj like

const obj = mock<MyModel>({ id: 'mem_123', createdAt: mock<Date>() })

but I don't love this, and other tests may require the date to be a specific value and be comparable to other dates.

Is this a bug? Is there a better way to write the expect() that is failing?

strictmock functionality

Trying to figure out whether there a straightforward manner to assert failure on unexpected mock invocations. Using the latest code an unexpected invocation results in an undefined, which in the case of Promises bubbles it's way through the SUT.

I currently employ a workaround where I hang assert raising implementations after my other .calledWith (the calledWiths are evaluated in order of declaration, so the fall through has to be the last one), e.g.

const mockApi = mock<APIClass>();

// mockApi being provided to SUT
mockApi.get
            .calledWith(CORRECT_API, "/some-path", anyObject())
            .mockResolvedValue(response);

// hook to catch errant mockApi calls
mockApi.get
            .mockImplementation(strictMockFallThrough);
            
sut = new Sut(mockApi);
// this should only hit CORRECT_API
expect(sut.refresh()).toStrictEqual([item1, item2, item3]);

However this is suboptimal as firstly the strictmock semantic has to be implemented in multiple places throughout a test rather than once at mock creation. The second problem is that whilst Jest itself uses exceptions for signalling test failure, if mocked method returns promises this exception is turned into a Promise.reject rather than test assertion. Perhaps something like:

expect(mockApi.get).unexpectedly.toHaveBeenCalledTimes(0);

Google Mock offers strictmock adapters, shows the gist.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.