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"`);
});
});
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 want to test the below module,
//module to test
import { methodFunction } from "./method"
export const toTest = () => {
const response = methodFunction();
}
//method module
export const methodFunction = () => {
return 1
}
How do I do this with Jest.mock()? I just want to see if methodFunction has been called. I can find examples but non work with named exports in this scenario.
//Attempt
jest.mock('./method', ()=> { ??? })
try with
import { methodFunction } from './method';
jest.mock('./method', ()=> {
return { methodFunction: jest.fn() }
})
describe('toTest', () => {
beforeAll(() => {
toTest()
})
it('should call methodFunction', () => {
expect(methodFunction).toHaveBeenCalled();
})
})
but keep in mind, that jest.fn() returns a function, which returns undefined and in order for it to return something else you'll have to use .mockReturnValue()
Edit: here's how you can mock the module partially
jest.mock('./method', ()=> {
return {
...jest.requireActual('./method'),
methodFunction: jest.fn()
}
})
I have a function which makes API call and based on what would return by the first API call it makes the second API call. But the first API always returns undefined
getTotalCount = async () => {
const { showCountCallBack, showCount } = this.props;
try {
const response = await showCount();
const count = isEmpty(response.result);
if (count) {
console.log(" success");
} else {
showCountCallBack({ ...this.state });
}
} catch (e) {
console.log("error");
}
};
describe("component", () => {
let shallowComponent;
let shallowComponentInstance;
const showCountMock = jest.fn(() => Promise.resolve({ result: [] }));
const showCountCallBackMock = jest.fn(() => Promise.resolve({ result: [] }));
beforeEach(() => {
showCountMock.mockReset();
shallowComponent = shallowWithTheme(
<Component
showCount={showCountMock}
showCountCallBack={showCountCallBackMock}
/>
);
shallowComponentInstance = shallowComponent.instance();
});
it("viewMapping", () => {
shallowComponentInstance.getTotalCount();
expect(showCountMock).toHaveBeenCalledTimes(1);
expect(showCountCallBackMock).toHaveBeenCalledTimes(1);
});
});
After struggling for hours. I have found the cause. It was mockReset causing this issue.
It reset return value as well. so I just removed the mockReset from code. Better to use mockClear here
I can't understand at all the utility of mockings. See, I have the next module:
function Code() {
this.generate = () => {
const result = 'code124';
return result;
};
}
module.exports = Code;
Now, I want to test it with jest:
const Code = require('../lib/code');
jest.mock('../lib/code', () => {
return jest.fn().mockImplementation(() => {
return {
generate: () => [1, 2, 3]
};
});
});
describe('Code', () => {
test('returns the code "code123"', () => {
const code = new Code();
expect(code.generate()).toBe('code123');
});
});
So... This test will be fine but... My code ain't so... what's the point about mocking if I can set a correct result even though my code is wrong?
You're NOT supposed to mock the unit you're testing. You're supposed to mock it's dependencies.
for example:
whenever you have a dependency in the implementation:
const dependency = require('dependency');
function Code() {
this.generate = () => {
const result = 'code' + dependency.getNumber();
return result;
};
}
module.exports = Code;
you'll be able to modify it's results to be able to test all scenarios without using the actual implementation of your dependency:
const dependency = require('dependency');
const Code = require('../lib/code');
jest.mock('dependency');
describe('Code', () => {
describe('when dependency returns 123', () => {
beforeAll(() => {
dependency.getNumber.mockReturnValue('123');
});
it('should generate code123', () => {
const code = new Code();
expect(code.generate()).toEqual('code123');
});
});
describe('when dependency returns 124', () => {
beforeAll(() => {
dependency.getNumber.mockReturnValue('124');
});
it('should generate code123', () => {
const code = new Code();
expect(code.generate()).toEqual('code124');
});
});
});