Sinon Spy for Non-Class Methods - javascript

I have a javascript file with a bunch of util functions in a file called utils.js
export const processListOfItems = (input): [] => {
let listOfItems = [];
for (var index = 0; index < rawPayload.length; ++index) {
listOfItems.push(someFunction(item));
}
return listOfItems;
};
someFunction is defined in utils.js as well.
For the test, I would like to stub "someFunction," but am having trouble figuring out how to do so. It looks like sinon.spy() might be the method I want, but it looks like it requires an object, of which I don't have one since it's just a utils file.
My ideal test would look something like this
describe('someFunction fails on an item', () => {
it('returns the array with the rest of the items', () => {
const items = ['hi', 'hello'];
// I want to make it such that, when we go into the getListOfItems code, we return 42 whenever we call someFunction, rather than going into the logic itself.
const someFunctionStub = sinon.stub(someFunction).returns(42);
expect(getListOfItems(items)).toEqual([42, 42]);
});
});

sinon.stub replaces a property on an object...
...and often the object is a module and the property is a function the module exports.
When the module export of a function is stubbed, any code that calls the module export of the function will then call the stub.
It isn't possible to stub someFunction in the code above since processListOfItems isn't calling the module export of someFunction, it is calling someFunction directly.
processListOfItems needs to call the module export of someFunction in order to be able to stub the call.
Here is a simple example to demonstrate using Node.js module syntax:
util.js
exports.func1 = () => {
return 'hello ' + exports.func2(); // <= use the module
}
exports.func2 = () => 'world';
util.test.js
const sinon = require('sinon');
const util = require('./util');
describe('func1', () => {
it('should work', () => {
const stub = sinon.stub(util, 'func2').returns('everyone');
expect(util.func1()).toBe('hello everyone'); // Success!
});
});
...and here is a simple example using ES6 module syntax:
util.js
import * as util from './util'; // <= import module into itself
export const func1 = () => {
return 'hello ' + util.func2(); // <= use the module
}
export const func2 = () => 'world';
util.test.js
import * as sinon from 'sinon';
import * as util from './util';
describe('func1', () => {
it('should work', () => {
const stub = sinon.stub(util, 'func2').returns('everyone');
expect(util.func1()).toBe('hello everyone'); // Success!
});
});
Note that ES6 modules can be imported into themselves since they "support cyclic dependencies automatically".

Related

Mock only one function from module but leave rest with original functionality

I only want to mock a single function (named export) from a module but leave the rest of the module functions intact.
Using jest.mock('package-name') makes all exported functions mocks, which I don't want.
I tried spreading the named exports back into the mock object...
import * as utils from './utilities.js';
jest.mock(utils, () => ({
...utils
speak: jest.fn(),
}));
but got this error:
The module factory of jest.mock() is not allowed to reference any out-of-scope variables.
The highlight of this answer is jest.requireActual(), this is a very useful utility that says to jest that "Hey keep every original functionalities intact and import them".
jest.mock('./utilities.js', () => ({
...jest.requireActual('./utilities.js'),
speak: jest.fn(),
}));
Let's take another common scenario, you're using enzyme ShallowWrapper and it doesn't goes well with useContext() hook, so what're you gonna do? While i'm sure there are multiple ways, but this is the one I like:
import React from "react";
jest.mock("react", () => ({
...jest.requireActual("react"), // import and retain the original functionalities
useContext: jest.fn().mockReturnValue({foo: 'bar'}) // overwrite useContext
}))
The perk of doing it this way is that you can still use
import React, { useContext } from "react" in your original code without worrying about converting them into React.useContext() as you would if you're using jest.spyOn(React, 'useContext')
The most straightforward way is to use jest.spyOn and then .mockImplementation(). This will allow all other functions in the module to continue working how they're defined.
For packages:
import axios from 'axios';
jest.spyOn(axios, 'get');
axios.get.mockImplementation(() => { /* do thing */ });
For modules with named exports:
import * as utils from './utilities.js';
jest.spyOn(utils, 'speak');
utils.speak.mockImplementation(() => { /* do thing */ });
Docs here: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname
jest.requireActual inside of jest.mock seems like the way to go, however I needed to add a proxy instead of the object spread to prevent the type error Cannot read properties of undefined (reading ...) which can occur in certain import scenarios.
This is the final result:
jest.mock('the-module-to-mock', () => {
const actualModule = jest.requireActual('the-module-to-mock')
return new Proxy(actualModule, {
get: (target, property) => {
switch (property) {
// add cases for exports you want to mock
// 👇👇👇
case 'foo': {
return jest.fn() // add `mockImplementation` etc
}
case 'bar': {
return jest.fn()
}
// fallback to the original module
default: {
return target[property]
}
}
},
})
})
For me this worked:
const utils = require('./utilities.js');
...
jest.spyOn(utils, 'speak').mockImplementation(() => jest.fn());
I took Rico Kahler's answer and created this general purpose function:
function mockPartially(packageName: string, getMocks: (actualModule: any) => any) {
jest.doMock(packageName, () => {
const actualModule = jest.requireActual(packageName);
const mocks = getMocks(actualModule);
return new Proxy(actualModule, {
get: (target, property) => {
if (property in mocks) {
return mocks[property];
} else {
return target[property];
}
},
});
});
}
and you use it like this for example to mock lodash:
mockPartially('lodash', (_actualLodash) => { //sometimes you need the actual module
return {
'isObject': () => true, //mock isObject
'isArray': () => true // mock isArray
}
});
Manual Mocks
You can create __mocks__ directory in the same level as utilities.js and then create a file with name utilities.js inside this directory.
utilities.js
const speak = () => "Function speak";
const add = (x, y) => x + y;
const sub = (x, y) => x - y;
module.exports = { speak, add, sub };
Now, keep everything as is and just mock the speak function.
__mocks__/utilities.js
const speak = jest.fn(() => "Mocked function speak");
const add = (x, y) => x + y;
const sub = (x, y) => x - y;
module.exports = { speak, add, sub };
And now you can mock utilities.js
utilities.test.js
const { speak, add, sub } = require("./utilities");
jest.mock("./utilities");
test("speak should be mocked", () => {
expect(speak()).toBe("Mocked function speak");
});
Mocking Node Modules
Create a directory named __mocks__ in the same level as node_modules and add a file 'axios.js' inside this directory.
__mocks__/axios.js
const axios = {
get: () => Promise.resolve({ data: { name: "Mocked name" } }),
};
module.exports = axios;
fetch.js
const axios = require("axios");
const fetch = async () => {
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/users/1"
);
return data.name;
};
module.exports = fetch;
With node modules you don't need to explicitly call jest.mock("axios").
fetch.test.js
const fetch = require("./fetch");
test("axios should be mocked", async () => {
expect(await fetch()).toBe("Mocked name");
});

Why is the Jest mock instance empty when mocking a Node.js module?

I'm having some problems with mocking, I've mocked a node module by adding a mocks/ssh2-sftp-client.ts file:
const mockSsh2SftpClient = jest.fn().mockImplementation(() => {
return {
connect: async () => {},
end: async () => {},
on: () => {}
}
})
export default mockSsh2SftpClient
This works, kinda. My tests run correctly using this mock, but in the tests SftpClient.mock.instances[0] is an empty mockConstructor {} object instead of this mock (ie. SftpClient.mock.instances[0].end is undefined). What am I doing wrong?
for reference, my testing code looks like this:
import { ConnectConfig } from 'ssh2'
import SftpClient from 'ssh2-sftp-client'
import { withSftp } from '../sftp'
// Type assertion to make TypeScript happy.
const MockSftpClient = SftpClient as jest.Mock<SftpClient>
describe(withSftp, () => {
const mockConnectionConfig: ConnectConfig = {}
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
MockSftpClient.mockClear()
})
it('should call the callback after successfully connecting', async () => {
const mockCallback = jest.fn()
// Instantiates SftpClient and calls connect, then the callback, then end.
await withSftp(mockConnectionConfig, mockCallback)
const mockInstance = MockSftpClient.mock.instances
expect(mockCallback).toHaveBeenCalledTimes(1)
expect(MockSftpClient.mock.instances[0].end).toHaveBeenCalledTimes(1)
})
})
The last fails because MockSftpClient.mock.instances[0].end is undefined, where it should be a function.
The mock constructor provided by Jest only records this as the instance so if your mock constructor returns a different object then that object won't be recorded in the instances array.
To get the behavior you are wanting just mock with a standard function and use this:
__mocks__/ssh2-sftp-client.ts
const mockSsh2SftpClient = jest.fn(function() {
this.connect = jest.fn();
this.end = jest.fn();
this.on = jest.fn();
});
export default mockSsh2SftpClient

jest mock a property that is an object and a function at the same time

I am trying to mock a property of an object that acts as an object and as a function at the same time. Here's the code:
index.js
const nock = require('nock');
async function myFunc() {
nock.back.setMode('param1');
const { nockDone } = await nock.back('param1', 'param2');
nock.enableNetConnect('param1');
return nockDone;
}
module.exports = { myFunc }
My goal is to mock the nock object in a way I can assert that when myFunc is called, nock.back is called with param1 and param2.
To do so I have the following test:
index.test.js
const nock = require('nock');
const subjectUnderTest = require('./index');
const nockBackImplementation = jest.fn();
nockBackImplementation.setMode = jest.fn();
const nockBackMock = jest.spyOn(nock, 'back');
nockBackMock.mockImplementation(() => nockBackImplementation);
describe('test', () => {
it('calls nock.back with the proper parameters', () => {
subjectUnderTest.myFunc();
expect(nockBackMock).toHaveBeenCalledWith('param1', 'param2');
});
});
For some reason, the test fails saying that the mock function has not been called and also gives this error:
UnhandledPromiseRejectionWarning: TypeError: nock.back.setMode is not a function
I'm not sure how to properly mock nock.
You're assigning the setMode mock to the function used as implementation, which I think is different from the actual nock.back mock. You can set it correctly this way
const nockBackMock = jest
.spyOn(nock, "back")
.mockResolvedValue({ nockDone: "test" });
nockBackMock.setMode = jest.fn();
// you also need to mock the enableNetConnect to avoid errors
const enableNetConnectMock = jest
.spyOn(nock, "enableNetConnect")
.mockReturnValue({});
describe("test", () => {
it("calls nock.back with the proper parameters", () => {
subjectUnderTest.myFunc();
expect(nockBackMock).toHaveBeenCalledWith("param1", "param2");
});
});

Mocking nested functions in jest [duplicate]

I'm trying to spy on a function that's called by another function, both of which reside in an external file and imported.
Funcs.spec.js:
import * as Funcs from './Funcs'
describe('funcA', () => {
it('calls funcB', () => {
jest.spyOn(Funcs, 'funcB')
Funcs.funcA()
expect(Funcs.funcB).toHaveBeenCalled()
}
}
Funcs.js:
export const funcA = () => {
funcB()
}
export const funcB = () => {}
For some reason the spy is not respected in scope of Funcs.js. What can I do to spy on funcB so I know funcA has called it?
Only methods can be spied. There is no way to spy on funcB if it's called directly like funcB() within same module.
In order for exported function to be spied or mocked, funcA and funcB should reside in different modules.
This allows to spy on funcB in transpiled ES module (module object is read-only in native ESM):
import { funcB } from './b';
export const funcA = () => {
funcB()
}
Due to that module imports are representations of modules, this is transpiled to:
var _b = require('./b');
var funcA = exports.funcA = function funcA() {
(0, _b.funcB)();
};
Where funcB method is tied to _b module object, so it's possible to spy on it.
The problem you describe is referenced on a jest issue.
A possible solution to your problem (if you want to keep the functions inside the same file) is to use CommonJS, consider the following example:
fns.js
exports.funcA = () => {
exports.funcB();
};
exports.funcB = () => {};
fns.spec.js
const fns = require("./fns");
describe("funcA", () => {
it("calls funcB", () => {
fns.funcB = jest.fn();
fns.funcA();
expect(fns.funcB).toBeCalled();
});
});

sinon stub not replacing function

I'm trying to use sinon stub to replace a function that might take along time. But when I run the tests, the test code doesn't seem to be using the sinon stubs.
Here is the code I'm trying to test.
function takeTooLong() {
return returnSomething();
}
function returnSomething() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('ok')
}, 1500)
})
}
module.exports = {
takeTooLong,
returnSomething
}
and this is the test code.
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'))
const expect = chai.expect
chai.should()
const db = require('./database')
const sinon = require('sinon')
require('sinon-as-promised')
describe('Mock the DB connection', function () {
it('should use stubs for db connection for takeTooLong', function (done) {
const stubbed = sinon.stub(db, 'returnSomething').returns(new Promise((res) => res('kk')));
const result = db.takeTooLong()
result.then((res) => {
expect(res).to.equal('kk')
sinon.assert.calledOnce(stubbed);
stubbed.restore()
done()
}).catch((err) => done(err))
})
I get an assertion error
AssertionError: expected 'ok' to equal 'kk'
+ expected - actual
-ok
+kk
What am I doing wrong? Why isn't the stub being used ? The test framework in Mocha.
Sinon stubs the property of the object, not the function itself.
In your case you are exporting that function within an object.
module.exports = {
takeTooLong,
returnSomething
}
So in order to properly call the function from the object, you need to replace your function call with the reference to the export object like :
function takeTooLong() {
return module.exports.returnSomething();
}
Of course based on your code, you can always refactor it :
var exports = module.exports = {
takeTooLong: function() { return exports.returnSomething() }
returnSomething: function() { /* .. */ }
}
You might want to have a look at Proxyquire to stub/spy directly exported functions.
https://www.npmjs.com/package/proxyquire/

Categories