I'm trying to mock a module import using Jest and I'm struggling for some reason. I've got the following code:
src/elastic.js
const getRolesFunc = elasticClient => async username => {
// Do some stuff
}
module.exports = { getRolesFunc };
src/handlerFactory.js
const { getRolesFunc } = require("../src/elastic");
const handlerFactory = elasticClient =>
async (event) => {
const getRolesAsync = getRolesFunc(elasticClient);
const roles = await getRolesAsync();
}
}
My test file currently looks like:
tests/handlerFactory.unit.test.js
const { handlerFactory } = require("../src/handlerFactory");
const { getRolesFunc } = require("../src/elastic");
jest.mock("../src/elastic", () => ({
getRolesFunc: jest.fn(),
}));
describe("handlerFactory", () => {
it("handler returns correct response", async () => {
getRolesFunc.mockImplementation(() => "foo");
// Call the handler to get our actual result
const handlerAsync = handlerFactory({});
const result = await handlerAsync(event);
});
});
At the moment however I'm getting an error in my test:
TypeError: getRolesFunc.mockImplementation is not a function
I've tried a few things none of which worked, this feels like the closest but I can't work out why the jest.mock isn't working correctly. I've looked at a few examples and still can't work out why this I can't get mocking working. Can anyone help point out what I've done wrong?
As you have module.exports = { getRolesFunc }; you need to below change in your code:
const { handlerFactory } = require("../src/handlerFactory");
const elasticObj = require("../src/elastic");
jest.mock("..src/elastic");
// in your example, now put below code:
elasticObj.getRolesFunc.mockImplementation(() => "foo");
Related
In the setup below, if I run the test as is, myFunc is not mocked when I debug into handler.
However, if instead I add this. in front of the myFunc call in handler, then the function is mocked and everything works as expected.
Can someone please explain why this is? I'm new to mocking and can't see it.
I know what this does, but why won't jest mock without it since I told it to mock that function in the module?
index.js
const aws = require('aws-sdk')
exports.handler = async function (event, context) {
let s;
switch (event.func) {
case "myFunc":
console.log('Executing myFunc');
//making the call: s = await this.myFunc.apply(null, [event.params]) will make the mock work.
s = await myFunc.apply(null, [event.params])
console.log(s);
return s;
/*cases...*/
default:
// default behaviour
}
async myFunc({p1, p2}){
/* do something */
return x
}
exports.myFunc = myFunc
}
index.spec.js
jest.mock('./index.js', () => {
const allAutoMocked = jest.createMockFromModule('./index.js')
const actual = jest.requireActual('./index.js')
return {
__esModules: true,
...allAutoMocked,
myFunc : jest.fn().mockImplementation(() => ({ mockedValue: 'test' })),
handler: actual.handler
}
})
let index = require("./index.js")
describe('Test myFunc', () => {
test('If myFunc function was called', async () => {
var event = { func: 'myFunc', params: { p1: xx, p2: false } };
const context = {};
const logMock = jest.fn((...args) => console.log(...args));
const data = await handler(event, context);
})
})
I did the tests in the following way, but it bothers me how I need to import an object with the functions, instead of just importing the functions.
this works
//service.test.js
const get = require('../../modules/bankUser/model/getRegisteredUser');
get.getRegisteredUser = jest.fn()
.mockImplementationOnce(async () => mock)
.mockImplementationOnce(async () => mockUpdated);
//service.js
const get = require('../model/getRegisteredUser');
const testedFunction () => {
const depositReciver = await get.getRegisteredUser(depositName, depositCpf);
}
this dosent
//service.test.js
const get = require('../../modules/bankUser/model/getRegisteredUser');
get.getRegisteredUser = jest.fn()
.mockImplementationOnce(async () => mock)
.mockImplementationOnce(async () => mockUpdated);
//service.js
const { getRegisteredUser } = require('../model/getRegisteredUser');
const testedFunction () => {
const depositReciver = await getRegisteredUser(depositName, depositCpf);
}
I'm triyng this (way):
//service.test.js
const get = require('../../modules/bankUser/model/getRegisteredUser');
jest.mock('../../modules/bankUser/model/getRegisteredUser');
get.mockImplementationOnce(() => ({ getRegisteredUser: () => mockObject }));
jest returns this:
TypeError: get.mockImplementationOnce is not a function
also I've tried to import like this
//service.test.js
const { getRegisteredUser } = require('../../modules/bankUser/model/getRegisteredUser');
jest.mock('../../modules/bankUser/model/getRegisteredUser');
getRegisteredUser.mockImplementationOnce(() => ({ getRegisteredUser: () => mockObject }));
EDIT
//getRegisteredUser.js
const { getConnection } = require('../../../global/connection');
const getRegisteredUser = async (userName, cpf) => {
const db = await getConnection('Data-Base');
const res = await db.collection('Collection')
.findOne({ userName, cpf });
return res;
};
module.exports = { getRegisteredUser };
The example you are pointing [https://jestjs.io/docs/es6-class-mocks#replacing-the-mock-using-mockimplementation-or-mockimplementationonce][1]
is related to the default export but I think you are using named export in your code.
There are multiple ways to do so
One Way
const get = require('../../modules/bankUser/model/getRegisteredUser');
jest.mock('../../modules/bankUser/model/getRegisteredUser');
get.getRegisteredUser.mockImplementationOnce(() => mockObject);
Second Way
jest.mock('../../modules/bankUser/model/getRegisteredUser', () => ({
...jest.requireActual('../../modules/bankUser/model/getRegisteredUser'),
getRegisteredUser: jest.fn().mockImplementation(() => mockObject),
}))
An Another Way
We can also mock a module using the __mocks__ directory which is inbuilt feature of jest
And at the end never forgot to call clearAllMocks() after each test else it may impact the output of other tests.
afterEach(() => {
jest.clearAllMocks();
});
I'm testing a cloud function named myCloudFn in my functions/send.js module. My tests are in functions/test/send.test.js:
// send.js
const { getCompareDate } = require('../utils.js');
async function myCloudFn(myTestDate) {
const compareDate = await getCompareDate(argToTest);
const isOlder = myTestDate < compareDate;
return isOlder ? 'older' : 'newer';
}
module.exports = { myCloudFn };
// send.test.js
const send = require('../send.js');
jest.mock('../utils', () => ({
getCompareDate: jest.fn(() => new Date('2020-01-31')) // default
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02')),
}));
describe('send.js', () => {
it('returns date comparison from myCloudFn()', async () => {
const myTestDate = '2020-03-03';
const returnValues = ['older', 'newer'];
const responsePromises = returnValues.map(() => send.myCloudFn(myTestDate));
const responses = await Promise.all(responsePromises);
expect(responses[0]).toBe(returnValues[0]);
expect(responses[1]).toBe(returnValues[1]);
});
});
The test functions correctly and passes as expected when I mock getCompareDate in this way, but for flexibility, I would rather provide custom input values for getCompareDate inside my tests and not 'globally'. Here's what I've tried:
const mockGetCompareDate = jest.fn();
jest.mock('../utils', () => ({
getCompareDate: mockGetCompareDate,
}));
it('returns date comparison from myCloudFn()', async () => {
mockGetCompareDate
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
const myTestDate = '2020-03-03';
const returnValues = ['older', 'newer'];
const responsePromises = returnValues.map(() => send.myCloudFn(myTestDate));
const responses = await Promise.all(responsePromises);
expect(responses[0]).toBe(returnValues[0]);
expect(responses[1]).toBe(returnValues[1]);
});
This method, however, is not working and throws an error:
ReferenceError: Cannot access 'mockGetCompareDate' before initialization
I've used this method with other tests as noted in the solution in this question, but I am not seeing similar results here. What am I missing?
Jest is hoisting the mocked function to the top of the module, and hence throws this error. The mock should instead be used right before you run the test. Further reading.
Try this:
const { getCompareDate } = require('../utils.js');
const mockGetCompareDate = jest.fn(() => new Date('2020-01-31'));
jest.mock('../utils.js', () => ({
__esModule: true,
getCompareDate: jest.fn(),
default: jest.fn()
}));
beforeAll(() => {
getCompareDate.mockImplementation(mockGetCompareDate);
});
To provide custom values do as you did before, when initialising the mock function. Source
Like this:
const mockGetCompareDate = jest.fn()
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
Or do as you did before inside the test. Source
Like this:
it('returns date comparison from myCloudFn()', async () => {
mockGetCompareDate
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
I would like to mock this piece of code that is using bookshelf js (with knex) with sinon.
const campaigns = await models.Campaign.forge()
.query((qb) => {
qb.where("account_id", accountId);
qb.andWhere("status", models.Campaign.STATUS.ACTIVE);
qb.andWhere(
"audience_create_trigger",
models.Campaign.AUDIENCE_CREATE_TRIGGER.ON_ENTER
);
})
.fetchAll();
How could I mock the inner queries inside the .query function.
I am a bit lost
Thank you very much!
I finally solved the problem using Sinon. This code is covering almost all the behavior
const assert = require("chai").assert
const sinon = require("sinon")
const models = require("../../../../models")
const query = {
query(func) {}
}
const qb = {
where(arg1, arg2) {},
andWhere(arg1, arg2) {}
}
const fetchAll = { async fetchAll() {} }
const forgeStub = sinon.stub(models.Campaign, "forge").returns(query)
const qbWhereStub = sinon
.stub(qb, "where")
.withArgs("account_id", accountId)
.returns(null)
const qbAndWhereStub = sinon.stub(qb, "andWhere").returns(null)
const queryStub = sandbox
.stub(query, "query")
.callsArgWith(0, qb)
.returns(fetchAll)
const fetchAllStub = sandbox.stub(fetchAll, "fetchAll").returns(campaigs)
//Calling the method
//Verify
assert.equal(qbWhereStub.callCount, 1)
assert.equal(qbAndWhereStub.callCount, 2)
assert.equal(forgeStub.callCount, 1)
assert.equal(queryStub.callCount, 1)
assert.equal(fetchAllStub.callCount, 1)
assert.isTrue(qbWhereStub.getCall(0).calledWithExactly("account_id", accountId))
assert.isTrue(qbAndWhereStub.getCall(0).calledWithExactly("status", models.Campaign.STATUS.ACTIVE))
assert.isTrue(
qbAndWhereStub
.getCall(1)
.calledWithExactly("audience_create_trigger", models.Campaign.AUDIENCE_CREATE_TRIGGER.ON_ENTER)
)
Try to use knex-mock-client to setup a test friendly knex instance.
The test will look much simpler.
import knex from "knex";
import { MockClient, getTracker } from "knex-mock-client";
describe("test", () => {
let db;
let tracker;
beforeAll(() => {
db = knex({ client: MockClient });
tracker = getTracker();
});
afterEach(() => tracker.reset());
it("should do something", () => {
tracker.on
.select(
(query) =>
query.sql.includes("table_name") && query.bindings.includes(accountId)
)
.responseOnce([]);
// execute query using db;
expect(tracker.history.select).toHaveLength(1);
});
});
I am having a hard time understanding what I am doing wrong.
I have a JS class as such:
export default class A {
constructor(repository) {
this._repository = repository;
}
async process(date) {
// ...
this._repository.writeToTable(entry);
}
}
and I am attempting to write a test that mocks the repository using sinon.mock
This is what I have so far:
describe('A', () => {
describe('#process(date)', () => {
it('should work', async () => {
const repository = { writeToTable: () => {} };
const mock = sinon.mock(repository);
const a = new A(repository);
await a.process('2017-06-16');
mock.expects('writeToTable').once();
mock.verify();
});
});
});
but it always fails saying that
ExpectationError: Expected writeToTable([...]) once (never called)
I've checked (added a console.log) and it is calling the object I defined on the test.
I ran this locally and read the documentation on sinonjs.org and you seem to be doing everything right.
I tried re-writing your example using a spy and ended up with something like this to get a passing test:
import sinon from "sinon";
import { expect } from "chai";
import A from "./index.js";
describe("A", () => {
describe("#process(date)", () => {
it("should work", async () => {
const repository = { writeToTable: sinon.spy() };
const a = new A(repository);
await a.process("2017-06-16");
expect(repository.writeToTable.calledOnce).to.be.true;
});
});
});