marchaos / jest-mock-extended

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

Unable to mock getter #29

Closed lebeier closed 4 years ago

lebeier commented 4 years ago

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

wihd commented 4 years ago

Although it would be better to integrate support for getters into the library, it is possible to mock a getter whilst using jest-mock-extended. Suppose you have an interface like this.

interface Strings {
    readonly size: number;
    item(index: number): string;
}

Just creating a mock for this interface using mock<Strings> will allow you to mock the item method. But since the type of size is not a function, an expression like mockObject.size.mockReturnValue(3) is a type error since you cannot call a method on an integer value.

However it is possible to define a property that uses a getter function directly on the implementation object that jest-mock-extended uses to record the state for its mock object. We need to pass an empty object into the mock<>() function so that we get a reference to this object. Consider the following code:

    // Construct the mockers
    const baseMock = {};
    const mockStrings = mock<Strings>(baseMock);
    const mockSizeGetter = jest.fn<number, []>();
    Object.defineProperty(baseMock, 'size', { get: mockSizeGetter });

This code creates an object mockStrings that mocks the Strings interface. The jest mock function mockSizeGetter is set to be the getter function for the size property. You have to call defineProperty on the baseMock object after creating the mocker. Otherwise the mock<>() function will attempt to alter it, which does not work since there is no setter function.

Now we can prepare the mockers to pretend to be a collection of three strings.

    mockSizeGetter.mockReturnValue(3);
    mockStrings.item
        .mockReturnValueOnce('zero')
        .mockReturnValueOnce('one')
        .mockReturnValueOnce('two');

The following code demonstrates that the getter have been mocked.

    const concatenate = (strings: Strings): string => {
        let result = '';
        for (let i = 0; i < strings.size; ++i) {
            if (i > 0) {
                result += ',';
            }
            result += strings.item(i);
        }
        return result;
    }

    expect(concatenate(mockStrings)).toEqual('zero,one,two');
lebeier commented 4 years ago

Thanks for this example. While I am not working with any projects that are using jest framework at the moment, I will give it a try in the future. =)