Mock a dependency's constructor Jest

66,367

Solution 1

The problem is how a module is being mocked. As the reference states,

Mocks a module with an auto-mocked version when it is being required. <...> Returns the jest object for chaining.

AWS is not module object but jest object, and assigning AWS.CloudFormation will affect nothing.

Also, it's CloudWatch in one place and CloudFormation in another.

Testing framework doesn't require to reinvent mock functions, they are already there. It should be something like:

const AWS = require("aws-sdk");
const fakePutMetricData = jest.fn()
const FakeCloudWatch = jest.fn(() => ({
    putMetricData: fakePutMetricData
}));                        
AWS.CloudWatch = FakeCloudWatch;

And asserted like:

expect(fakePutMetricData).toHaveBeenCalledTimes(1);

Solution 2

Above answer works. However, after some time working with jest I would just use the mockImplementation functionality which is useful for mocking constructors.

Below code could be an example:

import * as AWS from 'aws-sdk';

jest.mock('aws-sdk', ()=> {
    return {
        CloudWatch : jest.fn().mockImplementation(() => { return {} })
    }
});

test('AWS.CloudWatch is called', () => {
    new AWS.CloudWatch();
    expect(AWS.CloudWatch).toHaveBeenCalledTimes(1);
});

Note that in the example the new CloudWatch() just returns an empty object.

Solution 3

According to the documentation mockImplementation can also be used to mock class constructors:

// SomeClass.js
module.exports = class SomeClass {
  method(a, b) {}
};

// OtherModule.test.js
jest.mock('./SomeClass'); // this happens automatically with automocking
const SomeClass = require('./SomeClass');
const mockMethod= jest.fn();
SomeClass.mockImplementation(() => {
  return {
    method: mockMethod,
  };
});

const some = new SomeClass();
some.method('a', 'b');
console.log('Calls to method: ', mockMethod.mock.calls);

If your class constructor has parameters, you could pass jest.fn() as an argument (eg. const some = new SomeClass(jest.fn(), jest.fn());

Share:
66,367
Oliver Shaw
Author by

Oliver Shaw

I like computers

Updated on July 10, 2022

Comments

  • Oliver Shaw
    Oliver Shaw almost 2 years

    I'm a newbie to Jest. I've managed to mock my own stuff, but seem to be stuck mocking a module. Specifically constructors.

    usage.js

    const AWS = require("aws-sdk")
    cw = new AWS.CloudWatch({apiVersion: "2010-08-01"})
    ...
    function myMetrics(params) { 
      cw.putMetricData(params, function(err, data){})
    }
    

    I'd like to do something like this in the tests.

    const AWS = jest.mock("aws-sdk")
    class FakeMetrics {
      constructor() {}
      putMetricData(foo,callback) {
        callback(null, "yay!")
      }
    }
    
    AWS.CloudWatch = jest.fn( (props) => new FakeMetrics())
    

    However when I come to use it in usage.js the cw is a mockConstructor not a FakeMetrics

    I realise that my approach might be 'less than idiomatic' so I'd be greatful for any pointers.

    This is a minimal example https://github.com/ollyjshaw/jest_constructor_so

    npm install -g jest

    jest

  • Oliver Shaw
    Oliver Shaw over 6 years
    My confusion was certainly this bit. "AWS is not module object but jest object, and assigning AWS.CloudFormation will affect nothing."
  • David Schumann
    David Schumann almost 6 years
    How would you handle this if AWS was a class with static variables?
  • Estus Flask
    Estus Flask almost 6 years
    @DavidNathan Possibly with jest.spyOn. This depends on your case. It's unclear what are 'static variables'. Do you mean static methods? If you have a specific case in mind, consider asking a question that reflects your problem.
  • David Schumann
    David Schumann almost 6 years
    Sorry. I meant class variables. I opened a separate question: stackoverflow.com/questions/51027294/…
  • Alejandro Corredor
    Alejandro Corredor over 3 years
    This would leak to other code using the sdk right? Say I'm using CognitoIdentityServiceProvider in some other place, my tests for that would fail since the mock is not there. How do you reset this on a per test module basis?
  • Ariane
    Ariane almost 3 years
    Could you give an explicit example where constructor arguments are used to set properties for the class instance? I don't understand how to make it work so the end object has the expected properties. You mention passing jest.fn() as an argument, but where? How does it work?
  • David
    David almost 3 years
    I updated my answer. However, if you need to set properties for the class instance, then mocking is probably not what you want anyway?
  • Ariane
    Ariane almost 3 years
    Well, there's code that needs specific instance properties to exist, and getting the actual class isn't possible because its contents depend on specific Webpack loaders that I couldn't replicate. I ended up creating a central mock in mocks that's an actual amended class instead of a jest.fn(), and then enabling it with a simple jest.mock('path'). Only way I found to both have properties and obey constraints where they need to actually be class instances.
  • J W
    J W over 2 years
    @AlejandroCorredor you can call jest.resetModules or one of the other module methods