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();
});
});
Related
We have module within there is some initial logic which test that some value was configured, if not, it throws error.. then also the module provides methods. I want to describe this in specification (test), using Jest framework and test the feature. Here is simplified reproduced example:
// dependency.service.ts
export const something = {
method() {
return "methodValue";
}
};
export default function somethingElse() {
return "somethingElseValue";
}
// index.ts
import somethingElse, { something } from "./dependency.service";
const value1 = somethingElse();
const value2 = something.method();
console.log("ROOT somethingElse", value1);
console.log("ROOT something.method", value2);
// initialisation of module fails
if(value1 === 'throw_error' || value2 === 'throw_error') {
throw 'Some error';
}
export function smElse() {
const value = somethingElse();
console.log("somethingElse", value);
return value;
}
export function smMethod() {
const value = something.method();
console.log("somethingElse", value);
return value;
}
// index.spec.ts
import { smElse, smMethod } from './index';
jest.mock('./dependency.service', () => ({
__esModule: true,
default: jest.fn(() => 'MOCKED_somethingElseValue'),
something: {
method: jest.fn(() => 'MOCKED_methodValue'),
},
}));
describe('index', () => {
// some tests for happy paths
it('smElse returns mocked value', () => {
expect(smElse()).toMatchInlineSnapshot(`"MOCKED_somethingElseValue"`);
});
it('smMethod returns mocked value', () => {
expect(smMethod()).toMatchInlineSnapshot(`"MOCKED_methodValue"`);
});
it('smElse returns per test mocked value', () => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('ANOTHER_MOCKED_somethingElseValue');
expect(smElse()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_somethingElseValue"`);
});
it('smMethod returns per test mocked value', () => {
const something = require('./dependency.service').something;
something.method.mockReturnValueOnce('ANOTHER_MOCKED_methodValue');
expect(smMethod()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_methodValue"`);
});
// this is testing the throwing error in module root
it('throws error when somethingElse returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
it('throws error when something.method returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingMethodMocked = require('./dependency.service').something.method;
somethingMethodMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
});
"Try catch in isolation" solution does not work with async code as isolateModules method does not support async functions yet, reference: https://github.com/facebook/jest/issues/10428. I need alternative solution which would support async code.
Whole reproduced example repo here: https://github.com/luckylooke/jestTestModuleRootThrow/tree/main
EDIT:
I found out that some asynchronicity is supported by isolateModules, at least my use case, once I used expect.assertions(1) following test works as expected:
it('throws error when token data are not valid', async () => {
expect.assertions(1);
jest.isolateModules(async () => {
require('crypto-package').someMethod.mockReturnValueOnce(Promise.resolve({
decoderMethod: () => Promise.resolve('{ broken data }'),
}));
const { getTokenData } = require("./decoder.service");
await expect(getTokenData('34534xxxxxxxxxxxxxxx12628')).rejects.toMatchInlineSnapshot(`"Invalid token data"`);
});
});
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.
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 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());
});
});
});
});