Mock a dependency's constructor Jest - javascript

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

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.

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

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

Related

How to stub a module with sinon?

I need to stub hubspot module in order to test my apis.
I need to test this code:
const createCompany = async company => {
const hubspotClient = new hubspot.Client({
apiKey: HUBSPOT_KEY
});
//stuff
const companyObj = {
properties: {
//my properties
}
};
return await hubspotClient.crm.companies.basicApi.create(companyObj);
};
Here They show how to stub a class and access a method through its instance, but in my case I have multiple properties like .crm.companies.basicApi.create().
I tried doing:
getDataStub = sinon
.stub(hubspot.Client.prototype, 'crm.companies.BasicApi.create')
.resolves(fakeResponse);
But it doesn't work and it says TypeError: Cannot stub non-existent own property crm.companies.basicApi.create.
Do you have any hint on how to fix that?
You can create a HubspotClient instance outside of createCompany. Then export that instance so in the test file you could stub methods of it.
const hubspotClient = new hubspot.Client({
apiKey: HUBSPOT_KEY
});
// ...
const createCompany = async company => {
// ...
return await hubspotClient.crm.companies.basicApi.create(companyObj);
}
// ...
stub(hubspotClient.crm.companies.basicApi, 'create');

Mocking node_modules which return a function with Jest?

I am writing a typeScript program which hits an external API. In the process of writing tests for this program, I have been unable to correctly mock-out the dependency on the external API in a way that allows me to inspect the values passed to the API itself.
A simplified version of my code that hits the API is as follows:
const api = require("api-name")();
export class DataManager {
setup_api = async () => {
const email = "email#website.ext";
const password = "password";
try {
return api.login(email, password);
} catch (err) {
throw new Error("Failure to log in: " + err);
}
};
My test logic is as follows:
jest.mock("api-name", () => () => {
return {
login: jest.fn().mockImplementation(() => {
return "200 - OK. Log in successful.";
}),
};
});
import { DataManager } from "../../core/dataManager";
const api = require("api-name")();
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("200 - OK. Log in successful.");
expect(api.login).toHaveBeenCalledTimes(1);
});
});
What I find perplexing is that the test assertion which fails is only expect(api.login).toHaveBeenCalledTimes(1). Which means the API is being mocked, but I don't have access to the original mock. I think this is because the opening line of my test logic is replacing login with a NEW jest.fn() when called. Whether or not that's true, I don't know how to prevent it or to get access to the mock function-which I want to do because I am more concerned with the function being called with the correct values than it returning something specific.
I think my difficulty in mocking this library has to do with the way it's imported: const api = require("api-name")(); where I have to include an opening and closing parenthesis after the require statement. But I don't entirely know what that means, or what the implications of it are re:testing.
I came across an answer in this issue thread for ts-jest. Apparently, ts-jest does NOT "hoist" variables which follow the naming pattern mock*, as regular jest does. As a result, when you try to instantiate a named mock variable before using the factory parameter for jest.mock(), you get an error that you cannot access the mock variable before initialization.
Per the previously mentioned thread, the jest.doMock() method works in the same way as jest.mock(), save for the fact that it is not "hoisted" to the top of the file. Thus, you can create variables prior to mocking out the library.
Thus, a working solution is as follows:
const mockLogin = jest.fn().mockImplementation(() => {
return "Mock Login Method Called";
});
jest.doMock("api-name", () => () => {
return {
login: mockLogin,
};
});
import { DataManager } from "../../core/dataManager";
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("Mock Login Method Called");
expect(mockLogin).toHaveBeenCalledWith("email#website.ext", "password");
});
});
Again, this is really only relevant when using ts-jest, as using babel to transform your jest typescript tests WILL support the correct hoisting behavior. This is subject to change in the future, with updates to ts-jest, but the jest.doMock() workaround seems good enough for the time being.

ES6 imports and 'is not a constructor' in Jest.mock

Similar to Jest TypeError: is not a constructor in Jest.mock, except I am using ES6 imports - and the answer given to that question is not working on my situation.
Following the Jest .mock() documentation I am attempting to mock the constructor Client from the pg module.
I have a constructor, Client, imported from an ES6 module called pg. Instances of Client should have a query method.
import { Client } from "pg";
new Client({ connectionString: 'postgresql://postgres:postgres#localhost:5432/database' });
export async function doThing(client): Promise<string[]> {
var first = await client.query('wooo')
var second = await client.query('wooo')
return [first, second]
}
Here's my __tests__/test.ts
const log = console.log.bind(console)
jest.mock("pg", () => {
return {
query: jest
.fn()
.mockReturnValueOnce('one')
.mockReturnValueOnce('two'),
};
});
import { Client } from "pg";
import { doThing } from "../index";
it("works", async () => {
let client = new Client({});
var result = await doThing(client);
expect(result).toBe(['one', 'two'])
});
This is similar to the answer given in Jest TypeError: is not a constructor in Jest.mock, but it's failing here.
The code, just:
const mockDbClient = new Client({ connectionString: env.DATABASE_URL });
fails with:
TypeError: pg_1.Client is not a constructor
I note the docs mention __esModule: true is required when using default exports, but Client is not a default export from pg (I've checked).
How can I make the constructor work properly?
Some additional notes after getting an answer
Here's a slightly longer-form version of the answer, with comments about what's happening on each line - I hope people reading this find it useful!
jest.mock("pg", () => {
// Return the fake constructor function we are importing
return {
Client: jest.fn().mockImplementation(() => {
// The consturctor function returns various fake methods
return {
query: jest.fn()
.mockReturnValueOnce(firstResponse)
.mockReturnValueOnce(secondResponse),
connect: jest.fn()
}
})
}
})
When you mock the module, it needs to have the same shape as the actual module. Change:
jest.mock("pg", () => {
return {
query: jest
.fn()
.mockReturnValueOnce('one')
.mockReturnValueOnce('two'),
};
});
...to:
jest.mock("pg", () => ({
Client: jest.fn().mockImplementation(() => ({
query: jest.fn()
.mockReturnValueOnce('one')
.mockReturnValueOnce('two')
}))
}));

How to stub a libary function in JavaScript

For example, if I have main.js calling a defined in src/lib/a.js, and function a calls node-uuid.v1, how can I stub node-uuid.v1 when testing main.js?
main.js
const a = require("./src/lib/a").a
const main = () => {
return a()
}
module.exports = main
src/lib/a.js
const generateUUID = require("node-uuid").v1
const a = () => {
let temp = generateUUID()
return temp
}
module.exports = {
a
}
tests/main-test.js
const assert = require("assert")
const main = require("../main")
const sinon = require("sinon")
const uuid = require("node-uuid")
describe('main', () => {
it('should return a newly generated uuid', () => {
sinon.stub(uuid, "v1").returns("121321")
assert.equal(main(), "121321")
})
})
The sinon.stub(...) statement doesn't stub uuid.v1 for src/lib/a.js as the above test fails.
Is there a way to globally a library function so that it does the specified behavior whenever it gets called?
You should configure the stub before importing the main module. In this way the module will call the stub instead of the original function.
const assert = require("assert")
const sinon = require("sinon")
const uuid = require("node-uuid")
describe('main', () => {
it('should return a newly generated uuid', () => {
sinon.stub(uuid, "v1").returns("121321")
const main = require("../main")
assert.equal(main(), "121321")
})
})
Bear in mind that node-uuid is deprecated as you can see by this warning
[Deprecation warning: The use of require('uuid') is deprecated and
will not be supported after version 3.x of this module. Instead, use
require('uuid/[v1|v3|v4|v5]') as shown in the examples below.]
About how to stub that for testing would be a bit more harder than before as actually there is no an easy way to mock a standalone function using sinon
Creating a custom module
//custom uuid
module.exports.v1 = require('uuid/v1');
Requiring uuid from the custom module in your project
const uuid = require('<path_to_custom_module>');
Sinon.stub(uuid, 'v1').returns('12345');

Sinon Spy never called but it should be

Problem
I'm testing a custom redux middleware using Jest and SinonJS and more precisely I want to test if some functions are called on special conditions inside the middleware.
I use SinonJS for creating the spies and I run my tests with Jest. I initialised the spies for the specific functions I want to track and when I check if the spies has been called, the spies has not been even if it should be (manually tested).
Code
Here is the middleware I want to test :
import { Cookies } from 'react-cookie';
import setAuthorizationToken from './setAuthorizationToken';
let cookies = new Cookies();
export const bindTokenWithApp = (store) => (next) => (action) => {
// Select the token before action
const previousToken = getToken(store.getState());
// Dispatch action
const result = next(action);
// Select the token after dispatched action
const nextToken = getToken(store.getState());
if (previousToken !== nextToken) {
if (nextToken === '') {
setAuthorizationToken(false);
cookies.remove(SESSION_COOKIE_NAME, COOKIE_OPTIONS);
} else {
cookies.set(SESSION_COOKIE_NAME, nextToken, COOKIE_OPTIONS);
setAuthorizationToken(nextToken);
}
}
return result;
};
Here is my actual test
import { bindTokenWithApp } from './middleware';
import { Cookies } from 'react-cookie';
import sinon, { assert } from 'sinon';
import setAuthorizationToken from './setAuthorizationToken';
describe('bindTokenWithApp', () => {
const next = jest.fn();
const action = jest.fn();
let cookies = new Cookies();
it('removes cookies when there is no token', () => {
// My actual not working spies
const cookieSpy = sinon.spy(cookies.remove);
const authSpy = sinon.spy(setAuthorizationToken);
// Stub for the specific case. This code works,
// I console.logged in the middleware and I'm getting the below values
const getState = sinon.stub();
getState.onFirstCall().returns({ auth: { token: 'a token' } });
getState.onSecondCall().returns({ auth: { token: '' } });
const store = { getState: getState };
bindTokenWithApp(store)(next)(action);
assert.calledOnce(cookieSpy);
assert.calledOnce(authSpy);
// Output : AssertError: expected remove to be called once but was called 0 times
// AssertError: expected setAuthorizationToken to be called once but was called 0 times
cookieSpy.restore(); // <= This one works
authSpy.restore(); // TypeError: authSpy.restore is not a function
});
});
I've read SinonJS doc and a few StackOverFlow posts but without solutions. I also can't call authSpy.restore();. I think I do not initialise spies the right way and I'm misunderstanding a concept in SinonJS but I can't find which one !
The setAuthorizationToken signature is
(alias) const setAuthorizationToken: (token: any) => void
import setAuthorizationToken
I think it's a classical module so I can't figure out why I struggle with authSpy.restore();
The two spies you have actually have two different fixes, both with the same underlying problem. sinon.spy(someFunction) doesn't actually wrap someFunction itself, it returns a spy for it but doesn't perform any replacement.
For the first spy, there exists a shorthand to automatically wrap an object method: sinon.spy(cookie, 'remove') should do what you need.
For the second spy, it is more complicated as you need to wrap the spy around the default export of setAuthorizationToken. For that you will need something like proxyquire. Proxyquire is a specialized require mechanism that allows you to replace imports with your desired test methods. Here's a brief of what you'll need to do:
const authSpy = sinon.spy(setAuthorizationToken);
bindTokenWithApp = proxyquire('./middleware', { './setAuthorizationToken': authSpy});

Categories

Resources