Node.js/jest testing two exported functions. Calling one fails on uninvoked function - node.js

I have two functions I am exporting and importing to a test file. I am only calling function #1 from a test, but the test fails unexpectedly in function #2 without it being invoked. Example:
Below are the two exported functions.
export const validatePath = async (directory: string) => {
const configFile = glob.sync(path.join(PARENT_DIRECTORY, `/directory/${directoryPath}/config.+(yml|yaml)`));
const fullPath = path.join(PARENT_DIRECTORY, `/directory/${directoryPath}`);
if (!configFile || configFile.length !== 1) {
console.error(`Could not find a config.yml or config.yaml at ${fullPath}.`);
process.exit(1);
}
};
export const removePathPrefixes = (paths: string[]) => {
return paths.map(filePath => filePath.slice(filePath.indexOf('/removedPath'));
};
And the test file:
'use strict';
import * as previewFunctions from '../../utils/preview';
describe('Test preview', () => {
test('Test removePathPrefixes correctly removes /directory from path', () => {
// Arrange
let inputPaths = [
'/directory/somePath',
'/directory/someOtherPath',
];
// Act
let outputPaths = previewFunctions.removePathPrefixes(inputPaths);
// Assert
outputPaths.forEach((path, i) => {
expect(path.includes('/directory')).toBe(false);
});
});
});
The error output is as below, which you can see is from function #2 which i am not invoking:
Why is this function being invoked and failing? I have tried mocking the function locally in the test as well as globally in the test file. I do need to test this function as well so ideally I do not want to mock it globally. I tried importing each function as individual imports as well as this current syntax.

Related

How can I mock a function called by the function I am exercising? The functions are in the same file [duplicate]

I have one file called helper.js that consist of two functions
export const funcA = (key) => {
return funcB(key)
};
export const funcB = (key,prop) => {
return someObj;
};
I have my helper.spec.js to test the helper.js file functions.
import {funcA,funcB} from 'helper';
describe('helper', () => {
test('testFuncB', () => {
}
test('testFuncA', () => {
}
}
The test for funcB is pretty simple i just call it and expect someObj
The problem is to test funcA, in order to test it i want to mock the response of funcB.
I want testFuncB call the actual funcB and testFuncA call mocked funcB
How can i achieve funcB to be mocked and original in my two tests?
This is not a duplicate. It is a different case: they mock inner called functions only, if I remove the testFuncB then it will be the same but I must perform test on testFuncB too.
If an ES6 module directly exports two functions (not within a class, object, etc., just directly exports the functions like in the question) and one directly calls the other, then that call cannot be mocked.
In this case, funcB cannot be mocked within funcA the way the code is currently written.
A mock replaces the module export for funcB, but funcA doesn't call the module export for funcB, it just calls funcB directly.
Mocking funcB within funcA requires that funcA call the module export for funcB.
That can be done in one of two ways:
Move funcB to its own module
funcB.js
export const funcB = () => {
return 'original';
};
helper.js
import { funcB } from './funcB';
export const funcA = () => {
return funcB();
};
helper.spec.js
import * as funcBModule from './funcB';
import { funcA } from './helper';
describe('helper', () => {
test('test funcB', () => {
expect(funcBModule.funcB()).toBe('original'); // Success!
});
test('test funcA', () => {
const spy = jest.spyOn(funcBModule, 'funcB');
spy.mockReturnValue('mocked');
expect(funcA()).toBe('mocked'); // Success!
spy.mockRestore();
});
});
Import the module into itself
"ES6 modules support cyclic dependencies automatically" so it is perfectly valid to import a module into itself so that functions within the module can call the module export for other functions in the module:
helper.js
import * as helper from './helper';
export const funcA = () => {
return helper.funcB();
};
export const funcB = () => {
return 'original';
};
helper.spec.js
import * as helper from './helper';
describe('helper', () => {
test('test funcB', () => {
expect(helper.funcB()).toBe('original'); // Success!
});
test('test funcA', () => {
const spy = jest.spyOn(helper, 'funcB');
spy.mockReturnValue('mocked');
expect(helper.funcA()).toBe('mocked'); // Success!
spy.mockRestore();
});
});
Late answer but this should work.
Also you should test funcB in its own file and not inside the 'helper' tests.
import { funcB } from './funcB';
import { funcA } from './helper';
jest.mock('./funcB');
describe('helper', () => {
test('test funcA', () => {
const funcBSpy = jest.fn();
funcB.mockImplementation(() => funcBSpy());
funcA();
expect(funcBSpy).toHaveBeenCalledTimes(1);
});
});
import * as helper from 'helper';
describe('helper', () => {
it('should test testFuncA', () => {
const mockTestFuncB = jest.mock();
// spy on calls to testFuncB and respond with a mock function
mockTestFuncB.spyOn(helper, 'testFuncB').mockReturnValue(/*your expected return value*/);
// test logic
// Restore helper.testFuncB to it's original function
helper.testFuncB.mockRestore();
}
}
I create a kind of nameSpace to handle this issue:
let helper = {}
const funcA = (key) => {
return helper.funcB(key)
};
const funcB = (key,prop) => {
return someObj;
};
helper = { funcA, funcB }
module.exports = helper
and then mocking is obvious with jest.fn
You can use babel-plugin-rewire provided __set__ function to mock internal function.
Assuming you have set up babel-plugin-rewire.
helper.spec.js
import {funcA, __set__} as helper from './helper';
describe('helper', () => {
test('test funcA', () => {
__set__('funcB', () => {
return 'funcB return value'
})
expect(funcA()).toBe('funcB return value');
});
});
One advantage of this solution is you don't need to change any original code
I was able to get this working. I separated my helper and my main logic into two files like other solutions. In the test file, I had to mock the entire helper file.
const { doAdd } = require('./addHelper');
function add(a, b) {
return doAdd(a, b);
}
jest.mock('./addHelper');
// ...
it('should call doAdd', () => {
// hook into doAdd helper method and intercept its return value
jest.spyOn(helperModule, 'doAdd').mockReturnValue(11);
expect(addModule.add()).toEqual(11);
expect(helperModule.doAdd).toBeCalled();
});
Here is my solution:
https://github.com/davidholyko/jest-sandbox
You can do the following trick when you test the funcA:
1.Mock the funcB:
helper.funcB = jest.fn().mockImplementationOnce(() => <your data>);
2.Change the funcB(key) to this.funcB(key)
I had the same problem and worked! Full Code:
export const funcA = (key) => {
return this.funcB(key)
};
export const funcB = (key,prop) => {
return someObj;
};
Test Code:
import helper from 'helper';
describe('helper', () => {
test('testFuncB', () => {
...
}
test('testFuncA', () => {
helper.funcB = jest.fn().mockImplementationOnce(() => <your data>);
}
}
I think this might work
import * as helper from 'helper';
describe('helper', () => {
test('testFuncB', () => {
}
test('testFuncA', () => {
const mockTestFuncB = jest.mock();
// spy on calls to testFuncB and respond with a mock function
jest.spyOn(helper, 'testFuncB').mockImplementationOnce(mockTestFuncB);
// Do the testing ...
// Restore helper.testFuncB to it's original function
helper.testFuncB.mockRestore();
}
}

how to refer to a function when unit testing with NodeJS / Mocha

I'm making my first test with Mocha.
Dummy test is passing, but when I want to refer to my actual function that is in another file, it won't find it:
ReferenceError: main is not defined
I have a single index.js with :
async function main() {
function comparer(otherArray) {
return function (current) {
return otherArray.filter(function (other) {
return other.prm === current.prm && other.conso_prod === current.conso_prod
}).length === 0;
}
}
}
module.exports = main();
and in my test.js file, I do:
const {expect} = require('chai');
describe('Sum numbers', () => {
it('Compare 2 existing array', () => {
const meters = []
const perimeter = []
const onlyInMeters = meters.filter(main.comparer(perimeter));
expect(onlyInMeters).to.equal([]);
});
});
But when I refer to main.comparer, it can't find it:
ReferenceError: main is not defined
What am I forgetting? Sorry, I'm a NodeJS Noob!
It seems like you did not import the index.js file in test.js file. You are returning noting from main function as well.
Also, why are you exporting it like module.exports = main(); Instead you can do this:
// index.js
module.exports = {
comparer: (otherArray) => { ... }
}
// test.js
cosnt main = require('PATH_OF_index.js');
main.comparer();

Jest - Mocking and testing the node.js filesystem

I have created a function which basically loops over an array and create files. I'm starting to get into testing using Jest to have some extra security in place to make sure everything works however I'm experiencing some issues trying to mock the Node.js filesystem.
This is the function I wish to test - function.ts:
export function generateFiles(root: string) {
fs.mkdirSync(path.join(root, '.vscode'));
files.forEach((file) => {
fs.writeFileSync(
path.join(root, file.path, file.name),
fs.readFileSync(path.join(__dirname, 'files', file.path, file.name), 'utf-8')
);
});
}
const files = [
{ name: 'tslint.json', path: '' },
{ name: 'tsconfig.json', path: '' },
{ name: 'extensions.json', path: '.vscode' },
];
I've been reading around but can't really figure out how to test this with jest. No examples to look at. I've tried to install mock-fs which should be a simple way of getting up and running with a mock version of the Node.js FS module but I honestly don't know where to start. This is my first attempt at making a simple test - which causes an error, says 'no such file or directory' - function.test.ts:
import fs from 'fs';
import mockfs from 'mock-fs';
beforeEach(() => {
mockfs({
'test.ts': '',
dir: {
'settings.json': 'yallo',
},
});
});
test('testing mock', () => {
const dir = fs.readdirSync('/dir');
expect(dir).toEqual(['dir']);;
});
afterAll(() => {
mockfs.restore();
});
Anyone who can point me in the right direction?
Since you want to test you implementation you can try this:
import fs from 'fs';
import generateFiles from 'function.ts';
// auto-mock fs
jest.mock('fs');
describe('generateFiles', () => {
beforeAll(() => {
// clear any previous calls
fs.writeFileSync.mockClear();
// since you're using fs.readFileSync
// set some retun data to be used in your implementation
fs.readFileSync.mockReturnValue('X')
// call your function
generateFiles('/root/test/path');
});
it('should match snapshot of calls', () => {
expect(fs.writeFileSync.mock.calls).toMatchSnapshot();
});
it('should have called 3 times', () => {
expect(fs.writeFileSync).toHaveBeenCalledTimes(3);
});
it('should have called with...', () => {
expect(fs.writeFileSync).toHaveBeenCalledWith(
'/root/test/path/tslint.json',
'X' // <- this is the mock return value from above
);
});
});
Here you can read more about the auto-mocking

nodejs/mocha/chai as promise : variables used in expected async function initialized outside

I am brand new to mocha/chai and I spent 2 days trying to solve the following issue without any success (please note that the code below is just to present the concept, it is not the real one).
I have got a JS file called "api.js" in which some variables such as SERVER_URL are initialized at the top of the file through dotenv framework.
api.js :
const SERVER_URL = process.env.SERVER_URL;
async function startAPI () {
return new Promise ( (resolve, reject) => {
console.log(`${SERVER_URL}`);
resolve();
});
exports = {startAPI};
Now I have got "test.js" file in which :
test.js:
require('../api');
it('the test', async () => {
return await expect(api.startAPI()).to.be.fulfilled;
});
The problem is that SERVER_URL is undefined during the test and I cannot modify the api.js (as I am not the owner), just the test.js.
How can I run the test with the SERVER_URL variable set correctly (to process.env.SERVER_URL value from api.js) ?
Is there a solution without any refactoring ?
And if not what is the best solution ?
Experts, thanks in advance for your precious help
A way to improve testability is to use process.env.SERVER_URL instead of SERVER_URL where possible - or getServerUrl():
const getServerUrl = () => process.env.SERVER_URL;
This way process.env.SERVER_URL can be mocked at any point.
An alternative is to import module after process.env.SERVER_URL was mocked. This should involve decaching if there's more than one test that uses this module, because it won't be re-evaluated otherwise.
const decache = require('decache');
...
let originalServerUrl;
beforeEach(() => {
originalServerUrl = process.env.SERVER_URL;
});
beforeEach(() => {
process.env.SERVER_URL = originalServerUrl;
});
it('the test', async () => {
decache('../api');
process.env.SERVER_URL = '...';
const api = require('../api');
await expect(api.startAPI()).to.be.fulfilled;
});
If it's expected that there's no SERVER_URL in tests, it can be just discarded after it was mocked:
The easiest way would be just to set these variables when you run your test from CLI:
e.g. in npm scripts:
"scripts": {
"test": "SERVER_URL='http://example.com' mocha"
}
or directly from terminal:
$ SERVER_URL='http://example.com' npm test
But better solution would be mock environment variables in your tests with little refactoring. And need proxyquire to be installed. And actually async/await is not needed here.
const proxyquire = require('proxyquire').noPreserveCache() // noPreserveCache is important to always have refreshed script with new process.env.SERVER_URL in each test
const MOCKED_SERVER_URL = 'http://example.com'
describe('example', () => {
let initialServerUrl
let api
beforeEach(() => {
initialServerUrl= process.env
})
afterEach(() => {
process.env = initialServerUrl
})
it('fulfilled', () => {
process.env.USE_OTHER_CODE_PATH = MOCKED_SERVER_URL
api = proxyquire('../api', {})
return expect(api.startAPI()).to.be.fulfilled
})
it('rejected', () => {
process.env.USE_OTHER_CODE_PATH = ''
api = proxyquire('../api', {})
return expect(api.startAPI()).to.be.rejected
})
})
You can set .env variables with mocha using the following line:
env SERVER_URL=htt://api.yourserver.com/ mocha test
This way mocha knows what to expect from your process.env.SERVER_URL

Mock.mockImplementation() not working

I have a service class
Service.js
class Service {
}
export default new Service();
And I am trying to provide a mock implementation for this. If I use something like this:
jest.mock('./Service', () => { ... my mock stuff });
It works fine, however I'm not able to access any variables declared outside of the mock, which is a bit limiting as I'd like to reconfigure what the mock returns, etc.
I tried this (inspired by this other StackOverflow article: Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error)
import service from './Service';
jest.mock('./Service', () => jest.fn);
service.mockImplementation(() => {
return { ... mock stuff }
);
Unfortunately when I am trying to run this, I get the below error:
TypeError: _Service2.default.mockImplementation is not a function
I had same problem as #Janos, the other answers didn't help either. You could do two things :
If you need to mock only a function from Service, in your test file:
import service from './Service';
jest.mock('./Service', () => jest.fn());
service.yourFunction = jest.fn(() => { /*your mock*/ })
 
If you need to mock the entire Module:
Say your service.js is in javascript/utils, create a javascript/utils/_mocks_ and inside it create a service.js file, you can then mock the entire class in this file, eg:
const myObj = {foo: "bar"}
const myFunction1 = jest.fn(() => { return Promise.resolve(myObj) })
const myFunction2 = ...
module.exports = {
myFunction1,
myFunction2
}
then in your test file you just add:
jest.mock('./javascript/utils/service')
...functions exported from the mockfile will be then hit through your test file execution.
The mock is equal to jest.fn. You need to call jest.fn to create a mocked function.
So this:
jest.mock('./Service', () => jest.fn);
Should be:
jest.mock('./Service', () => jest.fn());
ran into similar issues and resolved it by using .mockImplementationOnce
jest.mock('./Service', () => jest.fn()
.mockImplementationOnce(() => {
return { ... mock stuff }
})
.mockImplementationOnce(() => {
return { ... mock other stuff }
})
);
now when you run another test it will return the second mock object.
You need to store your mocked component in a variable with a name prefixed by "mock" and make sure you return an object with a default property as you import your Service from the default in your "main.js" file.
// Service.js
class Service {
}
export default new Service();
// main.test.js (main.js contains "import Service from './Service';")
const mockService = () => jest.fn();
jest.mock('./Service', () => {
return {
default: mockService
}
});
I had similar problem, and the cause was that ".spec.js" file had an
import jest from "jest-mock";
After removing this line, it worked.
My mistake was that I was resetting the mock before each test. If you do that, be sure to reconfigure the mock implementation.
For example, change this:
let value;
let onPropertyChange: OnPropertyChangeCallback = jest.fn((changes: any) => {
value = changes["testValue"];
});
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
});
to this:
let value;
let onPropertyChange: OnPropertyChangeCallback;
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
onPropertyChange = jest.fn((changes: any) => {
value = changes["testValue"];
});
});

Resources