marchaos / jest-mock-extended

Type safe mocking extensions for Jest https://www.npmjs.com/package/jest-mock-extended
MIT License
828 stars 57 forks source link

Matcher to match object by values instead of reference #67

Closed TiMESPLiNTER closed 3 years ago

TiMESPLiNTER commented 3 years ago

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;
}