77

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

2

3 Answers 3

96

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.

2
  • 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? Commented Aug 5, 2020 at 18:17
  • @AlejandroCorredor you can call jest.resetModules or one of the other module methods
    – J W
    Commented Nov 7, 2021 at 14:32
37

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);
4
  • My confusion was certainly this bit. "AWS is not module object but jest object, and assigning AWS.CloudFormation will affect nothing." Commented Dec 2, 2017 at 13:05
  • 1
    How would you handle this if AWS was a class with static variables? Commented Jun 25, 2018 at 15:34
  • @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. Commented Jun 25, 2018 at 15:37
  • Sorry. I meant class variables. I opened a separate question: stackoverflow.com/questions/51027294/… Commented Jun 25, 2018 at 15:40
16

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());

3
  • 1
    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?
    – Ariane
    Commented May 27, 2021 at 13:26
  • I updated my answer. However, if you need to set properties for the class instance, then mocking is probably not what you want anyway?
    – David
    Commented May 27, 2021 at 15:05
  • 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.
    – Ariane
    Commented May 28, 2021 at 17:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.