I have a node script which goes on like
const { instance } = new SDK(id, authToken);
const data = await getAllModels(instance); // helper method which uses the sdk instance to return all models
items = await getItem(instance, id);
I have abstracted getAllModels and getItem into a helper module inside helper.js
exports.getAllModels = async (instance) => {
const { data } = await instance.getModels();
return data;
};
exports.getItem = async (instance, zuid) => {
const items = await instance.getItems(zuid);
return items;
};
I am trying to mock both the functions in my test so that I can expect the values based on my values.
jest.spyOn(helper, 'getAllModels').mockImplementation(() => {
console.log('Test');
return Promise.resolve('c');
});
console.log('Test');
jest.spyOn(helper, 'getItem').mockImplementation(() => {
console.log('Test 1');
return Promise.resolve('d');
});
const baseVal = await main(instance, token);
expect(baseVal).toBe("some value");
I can see that the mock values are not getting called and instead a direct call to the script is being used, what am I missing ?
From what I can see from your code, getAllModels and getItem are named exports from helper.js, which you can see from the use case you posted in your first code block.
So in your test file you could have something like the following:
const { getAllModels, getItem } = require('./helper');
jest.mock('./helper', () => {
return {
getAllModels: jest.fn(() => {
console.log('Test');
return Promise.resolve('c');
}),
getItem: jest.fn(() => {
console.log('Test 1');
return Promise.resolve('d');
}),
};
});
I think this is a cleaner implementation than using spyOn in this instance.
Related
I'm using jest with nodejs and sequelize for my models. For my testing, I wanted to mock the returned value of findAll to cover test scenarios. Sorry if this is a very newbie question but I'm at dead-end on this one.
init-models.js
module.exports = function initModels(sequelize) {
//model relationship code here
...
...
//end of model relationship code
return {
records,
anotherModel,
alsoAnotherModel
};
};
repository.js
const sequelize = require('../sequelize');
const initModels = require('../model/init-models');
let {
records,
anotherModel,
alsoAnotherModel
} = initModels(sequelize);
const fetchRecords = async () => {
console.info('Fetching records...');
return await records.findAll({sequelize parameters here});
}
repository.test.js This will work but needs the flexibility to mock findAll() return value/or throw Error
const repository = require('../../../src/db/repository/repository');
const initModels = require('../../../src/db/model/init-models');
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: jest.fn().mockImplementation(() => [1,2,3])
}
//the rest of the code for other models
}
}
});
describe('fetchRecords', () => {
beforeEach(()=> {
});
test('should return correct number of records', async () => {
const result = await repository.fetchRecords();
expect(result.size).toStrictEqual(3); //test passed
});
})
To allow mocking of results of findAll, I've tried extracting it so I can change the result per test scenario, but it was not working. What did I missed?
const mockRecordsFindAll = jest.fn();
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: () => mockRecordsFindAll
}
//the rest of the code for other models
}
}
});
describe('fetchRecords', () => {
beforeEach(()=> {
mockRecordsFindAll.mockReset()
});
test('should return correct number of records', async () => {
mockRecordsFindAll.mockImplementation(() => [1,2,3]); //should expect length 3
const result = await repository.fetchRecords();
expect(result.size).toStrictEqual(3); //fails, findAll was not mocked
});
})
The issue is mockRecordsFindAll is being returned instead of executed.
As #Gid machined just returning mockRecordsFindAll causes initialization issues (due to hoisting).
The solution for this case is using the decorator pattern to allow mockRecordsFindAll to be initialized afterward.
const mockRecordsFindAll = jest.fn();
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: function () {
return mockRecordsFindAll.call(this, arguments);
}
}
}
}
});
describe('fetchRecords', () => {
...
})
I'm getting below errors. Why I'm receiving response as undefined from the service?
Is there anything wrong I did for providing mock implementations?
Service:
export class SaveDataService{
async save() : Promise<any> {
try{
return this.someFunction()
} catch(ex){
throw new Error('some error occured')
}
}
async someFunction() : Promise<any>{
const response = {
"file" : "<htm><body>This is sample response</body></html>"
}
return Promise.resolve(response);
}
}
Test/Spec file:
import { SaveDataService } from "./save-data.service";
jest.mock('./save-data.service')
describe('tests for SaveDataService', () => {
it('when save method is called and success result is returned', async () => {
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
return Promise.resolve('Success Result')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
let spy = jest.spyOn(SaveDataService.prototype, 'someFunction');
let service = new SaveDataService();
let data = await service.save()
expect(data).toEqual('Success Result')
expect(spy).toHaveBeenCalled()
})
it('when save method is called and error is returned', async () => {
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
throw new Error('ERROR')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
let spy = jest.spyOn(SaveDataService.prototype, 'save');
let service = new SaveDataService();
service.save()
expect(spy).toThrowError('ERROR')
})
})
A mock replaces the dependency. You set expectations on calls to the dependent object, set the exact return values it should give you to perform the test you want, and/or what exceptions to throw so that you can test your exception handling code.
In this scenario, you are mocking save-data.service by calling jest.mock('./save-data.service'). So that your class may looks like this:
async save() : Promise<any> {
// do nothing or undefined
}
async someFunction() : Promise<any> {
// do nothing or undefined
}
So you must implement the body yourself to expect what exactly you want the method/function to do for you. You are mocking only the someFunction:
...
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
return Promise.resolve('Success Result')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
...
So when you call the save() method you still get nothing/undefined.
You are overwriting the whole behavior of the service that I think your test may not be useful. But you can fix your test this way:
import { SaveDataService } from "./save-data.service";
jest.mock('./save-data.service');
describe('tests for SaveDataService', () => {
beforeEach(() => {
SaveDataService.mockClear();
});
it('when save method is called and success result is returned', async () => {
const spy = jest
.spyOn(SaveDataService.prototype, 'save')
.mockImplementation(async () => Promise.resolve('Success Result'));
const service = new SaveDataService();
const data = await service.save();
expect(data).toEqual('Success Result');
expect(spy).toHaveBeenCalled();
})
it('when save method is called and error is returned', async () => {
const spy = jest
.spyOn(SaveDataService.prototype, 'save')
.mockImplementation(() => {
throw new Error('ERROR');
});
const service = new SaveDataService();
expect(service.save).toThrowError('ERROR');
expect(spy).toHaveBeenCalled();
});
});
I've been making dynamic dropdown box that each option has the table's name of BigQuery and I want to use return value (list) that is made inside .then method in function listTables(). However it seems not to work well . I'm new to Js so could you give any tips ?? Thank you so much.
function listTables() {
const {BigQuery} = require('#google-cloud/bigquery');
const bigquery = new BigQuery({
projectId: 'test_project',
});
const list = [];
bigquery
.dataset("test_table")
.getTables()
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);←I want to use this list outside a function
})
.catch(err => {
console.error('ERROR:', err);
});
}
listTables();
// select tag
let slt = document.getElementById("slt");
addTables(slt);
// return data
function getList() {
return new Promise(function (onFulliflled, onRejected) {
onFulliflled(list);
});
}
function addTables(slt) {
getList()
.then((list) => {
for (item of list) {
// optionを作成
let option = document.createElement("option");
option.text = item;
option.value = item;
// optionの追加
slt.appendChild(option);
}
})
.catch((err) => {
console.error("error", err);
});
}
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);
})
RESULT
[ 'test', 'test1', 'test2', 'test3' ]
You can do this in multiple ways.
Using calllback
function listTables(callback) {
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
callback(list);
})
//...
}
listTables(function(list){
});
Using promise or async/await
function listTables() {
return new Promise(function(resolve, reject){
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
resolve(list);
})
//...
});
}
// Promise
listTables().then(function(list){
});
//Async/await
var list = await listTables();
For the await to work you also need to run in within an async function. For example wrap it in async iife.
(async function(){
var list = await listTables();
})();
I don't use await myself so this is just from top of my head and might need some changes.
I am using jest for my backend unit testing.I need to mock third party library module methods using that.I tried the following code:
My controller file:
const edgejs = require('apigee-edge-js');
const apigeeEdge = edgejs.edge;
async get(req, res) {
const abc= await apigeeEdge.connect(connectOptions);
const Details = await abc.developers.get(options);
return res.status(200).send(Details);
}
test.spec.js
let edgejs = require('apigee-edge-js');
const ctrl = require('../../controller');
describe("Test suite for abc", () => {
test("should return ...", async() =>{
edgejs.edge = jest.fn().mockImplementationOnce(async () =>
{return {"connect":{"developers":{"get":[{}]}}}}
);
ctrl.get(req, res)
});
But its not mocking , its calling the actual library connect method. What i am doing wrong here. Please share your ideas. Thanks in advance.
WORKING CODE
jest.mock('apigee-edge-js', () => {
return { edge: { connect: jest.fn() } };
});
const edgejs = require('apigee-edge-js');
test("should return ...", async () => {
edgejs.edge.connect.mockImplementationOnce(() => Promise.resolve(
{"developers":{"get":[{}]}}
));
edgejs.edge.connect()
expect(edgejs.edge.connect).toBeCalled();
})
ERROR CODE:
jest.mock('apigee-edge-js', () => {
return { edge: { connect: jest.fn() } };
});
const Ctrl = require('../../controllers/controller'); ----> Extra line
const edgejs = require('apigee-edge-js');
test("should return ...", async () => {
edgejs.edge.connect.mockImplementationOnce(() => Promise.resolve(
{"developers":{"get":[{}]}}
));
const req = mockRequest();
const res = mockResponse();
await Ctrl.get(req, res) ---> Extra line
expect(edgejs.edge.connect).toBeCalled();
});
Receceivig erro : TypeError: edgejs.edge.connect.mockImplementationOnce is not a function
The mock doesn't affect anything because controller dereferences edgejs.edge right after it's imported, apigeeEdge = edgejs.edge. This would be different if it were using edgejs.edge.connect instead of apigeeEdge.connect.
Methods shouldn't be mocked as ... = jest.fn() because this prevents them from being restored and may affect other tests after that; this is what jest.spyOn is for. Furthermore, edge is an object and not a method.
Jest provides module mocking functionality. Third-party libraries generally need to be mocked in unit tests.
It should be:
jest.mock('apigee-edge-js', () => {
return { edge: { connect: jest.fn() } };
});
const ctrl = require('../../controller');
const edgejs = require('apigee-edge-js');
test("should return ...", async () => {
edgejs.edge.connect.mockImplementationOnce(() => Promise.resolve(
{"developers":{"get":[{}]}}
));
await ctrl.get(req, res)
...
});
I'm trying to track context through the async stack using node async_hooks. It works for most cases, however I have found this use case that I can't think how to resolve:
service.js:
const asyncHooks = require('async_hooks');
class Service {
constructor() {
this.store = {};
this.hooks = asyncHooks.createHook({
init: (asyncId, type, triggerAsyncId) => {
if (this.store[triggerAsyncId]) {
this.store[asyncId] = this.store[triggerAsyncId];
}
},
destroy: (asyncId) => {
delete this.store[asyncId];
},
});
this.enable();
}
async run(fn) {
this.store[asyncHooks.executionAsyncId()] = {};
await fn();
}
set(key, value) {
this.store[asyncHooks.executionAsyncId()][key] = value;
}
get(key) {
const state = this.store[asyncHooks.executionAsyncId()];
if (state) {
return state[key];
} else {
return null;
}
}
enable() {
this.hooks.enable();
}
disable() {
this.hooks.disable();
}
}
module.exports = Service;
service.spec.js
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await p.then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});
This test case will fail because the triggerAsyncId of the promise created when calling next is the executionAsyncId of the Promise.resolve() call. Which was created outside the current async stack and is a separate context. I can't see any way to marry the next functions async context with the context it was created in.
https://github.com/domarmstrong/async_hook_then_example
I wrote a very similar package called node-request-context with a blog post to explain it.
You haven't define any value for foo and you are not asking for any value when calling service.get() without any key. But I guess that was a minor mistake when you wrote the question.
The main issue you named was the location of Promise.resolve. I agree, there is no way to make it work. This is exactly the reason you've create the run function, so you will catch the executionAsyncId and track your code using it. Otherwise, you couldn't track any context.
Your code was just for testing but if you really need, you can cheat by using arrow function:
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = () => Promise.resolve();
await service.run(async () => {
service.set('foo', 'bar');
await p().then(() => {
assert.strictEqual('bar', service.get('foo'));
});
});
});
I found a solution, which is not perfect, but does work. Wrapping the original promise with Promise.all will resolve to the correct executionAsyncId. But it does rely on the calling code being aware of the promises context.
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await Promise.all([p]).then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});