5

Allow me to note that a similar question to this one can be found here, but the accepted answer's solution did not work for me. There was another question along the same lines, the answer of which suggested to directly manipulate the function's prototypes, but that was equally non-fruitful.

I am attempting to use Jest to mock this NPM Module, called "sharp". It takes an image buffer and performs image processing/manipulation operations upon it.

The actual implementation of the module in my codebase is as follows:

const sharp = require('sharp');

module.exports = class ImageProcessingAdapter {
    async processImageWithDefaultConfiguration(buffer, size, options) {
        return await sharp(buffer)
            .resize(size)
            .jpeg(options)
            .toBuffer();
    }
}

You can see that the module uses a chained function API, meaning the mock has to have each function return this.

The Unit Test itself can be found here:

jest.mock('sharp');
const sharp = require('sharp');

const ImageProcessingAdapter = require('./../../adapters/sharp/ImageProcessingAdapter');

test('Should call module functions with correct arguments', async () => {
    // Mock values
    const buffer = Buffer.from('a buffer');
    const size = { width: 10, height: 10 };
    const options = 'options';

    // SUT
    await new ImageProcessingAdapter().processImageWithDefaultConfiguration(buffer, size, options);

    // Assertions
    expect(sharp).toHaveBeenCalledWith(buffer);
    expect(sharp().resize).toHaveBeenCalledWith(size);
    expect(sharp().jpeg).toHaveBeenCalledWith(options);
});

Below are my attempts at mocking:

Attempt One

// __mocks__/sharp.js
module.exports = jest.genMockFromModule('sharp');

Result

Error: Maximum Call Stack Size Exceeded

Attempt Two

// __mocks__/sharp.js
module.exports = jest.fn().mockImplementation(() => ({
    resize: jest.fn().mockReturnThis(),
    jpeg: jest.fn().mockReturnThis(),
    toBuffer:jest.fn().mockReturnThis()
}));

Result

Expected mock function to have been called with:
      [{"height": 10, "width": 10}]
But it was not called.

Question

I would appreciate any aid in figuring out how to properly mock this third-party module such that I can make assertions about the way in which the mock is called.

I have tried using sinon and proxyquire, and they don't seem to get the job done either.

Reproduction

An isolated reproduction of this issue can be found here.

Thanks.

1 Answers1

5

Your second attempt is really close.

The only issue with it is that every time sharp gets called a new mocked object is returned with new resize, jpeg, and toBuffer mock functions...

...which means that when you test resize like this:

expect(sharp().resize).toHaveBeenCalledWith(size);

...you are actually testing a brand new resize mock function which hasn't been called.

To fix it, just make sure sharp always returns the same mocked object:

__mocks__/sharp.js

const result = {
  resize: jest.fn().mockReturnThis(),
  jpeg: jest.fn().mockReturnThis(),
  toBuffer: jest.fn().mockReturnThis()
}

module.exports = jest.fn(() => result);
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • Thank you so much. I'm going to go try this out in a minute. I've been stuck on this (and almost refactored my entire app to use Dependency Injection) for three days. I'll let you know how it goes and then accept your answer, although your reasoning makes perfect sense already. I appreciate it. –  Jun 14 '19 at 02:52
  • Glad to help :) (and nice job on your question...very clear and well written) – Brian Adams Jun 14 '19 at 02:56
  • It works beautifully, thanks. Also, thanks for the compliment on the question - I've always struggled to have my questions well received by the SO community. Your answer was clear and well written as well. –  Jun 14 '19 at 02:59
  • I have updated the README of the Repro I provided, by the way, to reflect your solution. See here: https://github.com/JamieCorkhill/Reproductions/blob/master/JestMockingIssueRepro/readme.md –  Jun 14 '19 at 03:10