How to mock fs.readdir in jest node.js - node.js

In my node.js app, I've written code to read files from a directory using await fs.readdir. When I try to test the code using jest, I can't mock the readdir function. I'm attaching the code.
const util = require('util');
const readdir = util.promisify(fs.readdir);
async function test(){
const files = await readdir("directory path")
return 'success'
}
How to mock readdir using jest
I've tried the below code, it doesn't seem to work
it('test read', async() => {
jest.spyOn(util, 'promisify').mockReturnValueOnce(fs.readdir);
await readdir.mockReturnValueOnce(files);
await expect(await Files.test()).toEqual("success");
})
I'm getting this error, ENOENT: no such file or directory, scandir ''. How to fix this? any ideas?

You can use the fs/promises module, so you don't need to promisify fs functions
// readdir.js
const fs = require('fs/promises');
async function load(directoryPath) {
const files = await fs.readdir(directoryPath);
return 'success';
}
module.exports = { load };
Notice the jest.mock instruction:
// readdir.test.js
const { load } = require('./readdir');
const fs = require('fs/promises');
jest.mock('fs/promises', () => ({
readdir: jest.fn(),
}));
it('calls fs.readdir function with the correct argument', async () => {
fs.readdir.mockResolvedValue();
await load('my-path');
expect(fs.readdir).toHaveBeenCalledWith('my-path');
});
it('returns correct result', async () => {
fs.readdir.mockResolvedValue();
const result = await load('x');
expect(result).toEqual('success');
});
It runs correctly on my local machine, so if it does not run ok there, might be a configuration issue, not really a problem mocking the fs module

Related

Why is my resetAllMocks not working in jest

The second expect(fs.writeFile).toHaveBeenCalledTimes(1) (in describe('Guid for MPX') returns an error because the writeFile has been called twice. In theory, jest.ResetAllMocks should take care of this but it doesn’t.
'use strict';
const fs = require('fs').promises;
const path = require('path');
const guidForMpxInvalid = require('../json/guid-for-Mpx-invalid.json')
const data = require('../../../data/sandbox-data-model.json');
jest.mock('fs', () => ({
promises: {
writeFile: jest.fn(),
},
}));
const {
writeData,
createGuidForMpx,
createMpxForGuid,
} = require('../app/transform');
const directoryPath = path.join(__dirname, '../../../wiremock/stubs/mappings');
describe('Write file', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should write a file', async () => {
const result = await writeData(guidForMpxInvalid, 'guid-for-Mpx-invalid-Mpx.json');
expect(result).toEqual('guid-for-Mpx-invalid-Mpx.json written');
expect(fs.writeFile).toHaveBeenCalledTimes(1);
});
});
describe('Guid for MPX', () => {
it('should create JSON file for the GUID of a particular MPX', async ()=>{
const result = await createGuidForMpx(data.Customers[0].guid, data.Customers[0].Customer_Overlays.core.Personal_Details.MPX);
expect(result).toEqual('guid-for-Mpx-AB123456B.json written');
expect(fs.writeFile).toHaveBeenCalledTimes(1);
});
});
The code being called:
const writeData = async (data, file) => {
const directoryPath = path.join(__dirname, '../../wiremock/stubs/mappings');
try {
fs.writeFile(`${directoryPath}/${file}`, data);
return `${file} written`
} catch (err) {
return err;
}
};
I was experiencing the same problem until I placed jest.resetAllMocks(); inside afterEach like so:
afterEach(() => {
jest.resetAllMocks();
});
I eventually got this working by creating a spy for the writefile at the start of each test and clearing it when the test is done:
it('should write a file', async () => {
const writeFileSpy = jest.spyOn(fs, 'writeFile');
const result = await writeData(guidForMPXInvalid, 'guid-for-mpx-invalid-mpx.json');
expect(result).toEqual('guid-for-mpx-invalid-mpx.json written');
expect(writeFileSpy).toHaveBeenCalledTimes(1);
writeFileSpy.mockClear();
});
});
Same thing here. I had to use spyOn as well, which is probably better practice.
All should beware when not using spyOn with complex libraries, double check that your reset works, but safe practice is to manually restore the function you mocked.
There is an issue it seems, perhaps because how fs/promises is included. fs.promises is a Getter function and is lazy loaded from internal/fs/promises, and jest is seemingly unable to clean lazy loaded modules with jest.resetModules?
See related note by #john-james regarding moduleNameMapper:
Jest not working with fs/promises typescript
Another documented error with resetModules():
https://github.com/facebook/jest/issues/11632

Sinon.restore not working for stubbing and testing AWS functions

So I'm trying to write a few tests for testing an AWS wrapper library that I have been writing.
The tests are running individually without any issues, but won't all run as one 'describe' block.
const AWS_REGION = 'eu-west-2';
const aws = require('aws-sdk');
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
// These help:
// https://stackoverflow.com/questions/26243647/sinon-stub-in-node-with-aws-sdk
// https://stackoverflow.com/questions/61516053/sinon-stub-for-lambda-using-promises
describe('SQS Utilities Test', () => {
afterEach(() => {
sinon.restore();
});
it('should add to SQS', async () => {
sinon.stub(aws.config, 'update');
const sqs = {
sendMessage: sinon.stub().returnsThis(),
promise: sinon.stub()
};
sinon.stub(aws, 'SQS').callsFake(() => sqs);
// these use the above stubbed version of aws
const AWSUtilities = require('../index').AWSUtilities;
const awsUtilities = new AWSUtilities(AWS_REGION);
const response = await awsUtilities.postToSQS('https://example.com', { id: 1}, 'chicken');
expect(sqs.sendMessage).to.have.been.calledOnce;
});
it('should get from SQS', async () => {
sinon.stub(aws.config, 'update');
const sqs = {
receiveMessage: sinon.stub().returnsThis(),
promise: sinon.stub()
};
sinon.stub(aws, 'SQS').callsFake(() => sqs);
// these use the above stubbed version of aws
const AWSUtilities = require('../index').AWSUtilities;
const awsUtilities = new AWSUtilities(AWS_REGION);
const response = await awsUtilities.getFromSQS('https://example.com');
expect(sqs.receiveMessage).to.have.been.calledOnce;
});
...
What I noticed, is that in the second test, the error I am getting is sqs.receiveMessage is not a function, which means that the second test is using the sqs object from the first test (I can further verify this as the error changes if I add receiveMessage to the first test sqs object).
Is this a bug in sinon restore, or have I written something incorrectly? Here is the whole library: https://github.com/unegma/aws-utilities/blob/main/test/SQSTests.spec.js
This is not an issue with Sinon. This an issue of how you are stubbing AWS SDK. Let's break down what's happening within the code you have shared.
const sqs = {
sendMessage: sinon.stub().returnsThis(),
promise: sinon.stub()
};
sinon.stub(aws, 'SQS').callsFake(() => sqs);
// these use the above stubbed version of aws
const AWSUtilities = require('../index').AWSUtilities;
This code does the following
Stub SQS of aws.
Load AWSUtilities.js (based on the source code in github)
AWSUtilities.js does the following as soon as its loaded
const aws = require('aws-sdk');
const sqs = new aws.SQS();
// code removed to demo the concept
The above code creates an internal sqs object, which in this case is made using the stubbed aws module. In node once a module is loaded using require it's cached in memory i.e the above code executes only once.
So when the first it() executes it in turn loads AWSUtilities.js for the first time and is cached. Any subsequent calls gets the cached version. When you call sinon.restore it only restores the SQS function of aws module it doesn't restore the sqs object that was created within AWSUtilities.js.
I hope that explains the reason for the behavior that you are seeing.
There are multiple ways to fix this issue. Dependency injection, using modules like proxyquire, rewire, stubbing aws from a central location before all test cases etc.
The following is an option to fix it in just the test cases shown here.
describe('SQS Utilities Test', () => {
let AWSUtilities, sqsStub;
before(() => {
sinon.stub(aws.config, 'update');
sqsStub = {
sendMessage: sinon.stub().returnsThis(),
receiveMessage: sinon.stub().returnsThis(),
promise: sinon.stub()
};
sinon.stub(aws, 'SQS').callsFake(() => sqs);
AWSUtilities = require('../index').AWSUtilities;
});
after(() => {
sinon.restore();
});
it('should add to SQS', async () => {
const awsUtilities = new AWSUtilities(AWS_REGION);
const response = await awsUtilities.postToSQS('https://example.com', { id: 1}, 'chicken');
expect(sqsStub.sendMessage).to.have.been.calledOnce;
});
it('should get from SQS', async () => {
const awsUtilities = new AWSUtilities(AWS_REGION);
const response = await awsUtilities.getFromSQS('https://example.com');
expect(sqsStub.receiveMessage).to.have.been.calledOnce;
});
});

Testing functions with properties using Jest and Sinon

am trying to write tests to test streams on my app, dealing with fs library with its createWriteStream function, so the stub i created is as follows:
writeStreamStub = sinon.stub()
onceStreamEventStub = sinon.stub()
endStreamStub = sinon.stub()
onStreamStub = sinon.stub()
createStreamStub = sinon.stub(fs, 'createWriteStream').returns({
write: writeStreamStub,
once: onceStreamEventStub,
end: endStreamStub,
on: onStreamStub
})
So now I can test for whether the functions are called and the returned functions are also called. But I am using the --coverage flag and the code of the callbacks of the returned functions is not covered, the write method is called inside a process.nextTick and I have no idea how to go about this. Is it possible to cover the whole code and the code inside the callbacks, and if so, how do I go about it. Thanks in advance.
N.B. The variables are globaly declared
If there's no cogent reason to use both sinon and jest, I'd recommend just using one library. If you decide to go with jest, here's a simple example. Assume you have a class like
const fs = require('fs');
module.exports = class FileWriter {
constructor() {
this.writer = fs.createWriteStream('./testfile.txt');
}
writeFile() {
process.nextTick(() => {
this.writeContent('hello world');
});
}
writeContent(content) {
this.writer.write(content);
this.writer.end();
}
};
and in your unit-test you want to mock the behaviour of all the used fs-functions (createWriteStream, writer, end in this case) and just check if they are called with the correct arguments. You could do this with something like this:
const fs = require('fs');
const FileWriter = require('./FileWriter');
// use this to have mocks for all of fs' functions (you could use jest.fn() instead as well)
jest.mock('fs');
describe('FileWriter', () => {
it('should write file with correct args', async () => {
const writeStub = jest.fn().mockReturnValue(true);
const endStub = jest.fn().mockReturnValue(true);
const writeStreamStub = fs.createWriteStream.mockReturnValue({
write: writeStub,
end: endStub,
});
const fileWriter = new FileWriter();
fileWriter.writeFile();
await waitForNextTick();
expect(writeStreamStub).toBeCalledWith('./testfile.txt');
expect(writeStub).toBeCalledWith('hello world');
expect(endStub).toHaveBeenCalled();
});
});
function waitForNextTick() {
return new Promise(resolve => process.nextTick(resolve));
}

Mocha complains about done() even though it is used

I am writing some tests of Solidity using Mocha module. The test fails below with this error despite the fact that the done() function is called and the promise is resolved (the commented out console.log() statements show that the Promise from the included module compile.js indeed resolves).
Perhaps I'm not interpreting the error correctly? I am new to Node.js, so my apologies if I cooked up a mess.
"before each" hook for "Deploy a contract":
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const web3 = new Web3(ganache.provider());
let accounts;
let inbox;
beforeEach(async (done) => {
// Get a list of all accounts
accounts = await web3.eth.getAccounts();
// console.log(accounts);
const generate = require('../compile');
await generate()
.then(async data => {
var interface = data.interface;
var bytecode = data.bytecode;
// console.log('ABI ' + interface);
// console.log('BIN ' + bytecode);
inbox = await new web3.eth.Contract(JSON.parse(interface))
.deploy({data: bytecode, arguments: ['Greetings!']})
.send({from: accounts[0], gas: '1000000'});
});
done();
});
describe('Inbox testing', () => {
it('Deploy a contract', () => {
console.log('Contract ' + inbox);
});
});
The function generate() imported from compile.js returns promise
function generate() {
return new Promise((resolve, reject) => {
...
})
})
}
module.exports = generate;
You cannot use a done callback with an async function in Mocha. Also, it's not a good idea to pass an async function to .then. I would refactor the test function to use asynchronous style code only.
beforeEach(async () => {
// Get a list of all accounts
const accounts = await web3.eth.getAccounts();
// console.log(accounts);
const generate = require('../compile');
const data = await generate();
var interface = data.interface;
var bytecode = data.bytecode;
// console.log('ABI ' + interface);
// console.log('BIN ' + bytecode);
inbox = await new web3.eth.Contract(JSON.parse(interface))
.deploy({data: bytecode, arguments: ['Greetings!']})
.send({from: accounts[0], gas: '1000000'});
});
I think mocha might be going crazy because you need to manually close your web3 connection after running your tests. Try calling disconnect after your tests run:
after(done => {
web3.currentProvider.disconnect()
done();
}

Unit test with sinon fake does not resolve promise

I'm learning nodejs and wrote this wrapper for a shelljs function, which in practice seems to work as intended.
/**
* Wrapper for Shelljs.exec to always return a promise
*
* #param {String} cmd - bash-compliant command string
* #param {String} path - working directory of the process
* #param {Object} _shell - alternative exec function for testing.
* #returns {String}
* #throws {TypeError}
*/
function shellExec(cmd, path, _shell = shelljs){
if( typeof _shell.exec !== "function") throw new TypeError('_shell.exec must be a function');
return new Promise((resolve, reject) => {
let options = { cwd: path, silent: true, asyc: true }
// eslint-disable-next-line no-unused-vars
return _shell.exec(cmd, options, (code, stdout, stderr) => {
// shelljs.exec does not always return a code
if(stderr) {
return reject(stderr);
}
return resolve(stdout);
});
});
}
However when I attempt to unit test it, the function times out. I have read the mochajs docs about async code, promises or async/await in tests. I want to use a sinon fake that returns a promise which I know works. Mocha tells me the error is that the function is not returning a promise via the error Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. I imagine I have constructed the fake improperly but I cannot see how else I should have done this.
const { expect, use } = require('chai');
const sinon = require('sinon');
const sinonChai = require("sinon-chai");
const utils = require('../utility/exec');
use(sinonChai);
it('sinon fake should resolve', async () =>{
const fake = sinon.fake.resolves('resolved');
const result = await fake();
expect(result).to.equal('resolved');
});
describe('Utility Functions', () =>{
describe('shellExec', () =>{
it('should accept an alternate execute function', async () =>{
const fakeShell = { exec: sinon.fake.resolves('pass') };
const result = await utils.shellExec('pwd', 'xyz', fakeShell);
expect(result).to.equal('pass');
expect(fakeShell.exec).to.have.been.calledOnce;
});
});
});
You function is a little complex but nothing sinon can't handle with stubs. See https://sinonjs.org/releases/v1.17.7/stubs/ for more info but what you should use is callsArgOnWith before the function.
Instead of setting exec to return a promise you need to set it as a stub. This way you can call the callback using the callsArgOnWith function when it is encountered.
I've changed your test so it now passes by changing the fake exec function to return a stub const fakeShell = { exec: sinon.stub() }; and adding the line fakeShell.exec.callsArgOnWith(2, null, null, 'pass', null) before running your function
const { expect, use } = require('chai');
const sinon = require('sinon');
const sinonChai = require("sinon-chai");
const utils = require('./main');
use(sinonChai);
it('sinon fake should resolve', async () =>{
const fake = sinon.fake.resolves('resolved');
const result = await fake();
expect(result).to.equal('resolved');
});
describe('Utility Functions', () =>{
describe('shellExec', () =>{
it('should accept an alternate execute function', async () =>{
const fakeShell = { exec: sinon.stub() };
fakeShell.exec.callsArgOnWith(2, null, null, 'pass', null)
const result = await utils.shellExec('pwd', 'xyz', fakeShell);
expect(result).to.equal('pass');
expect(fakeShell.exec).to.have.been.calledOnce;
});
});
});
your _shell.exec is just a callback function, It's not a Promise. That's why when you fake shell.exec to be a promise, your resolve will never been called. I think you need to fake your fakeShell to something like this:
const fakeShell = {
exec: (cmd, options, cb) => {
cb(true, 'pass', null);
}
};

Resources