Sinon crypto stub for method within a callback - javascript

I'm trying to test a simple function that generates a random name using the nodejs crypto library. I'm using sinon to stub out a method call within the callback of pseudoRandomBytes but the stub doesn't seem to be called. Example:
getFileName.js
const crypto = require('crypto');
module.exports = (req, file, cb) => {
crypto.pseudoRandomBytes(32, (err, raw) => {
try{
cb(err, err ? undefined : crypto.createHash('MD5').update(raw).digest('hex'));
} catch(err) {
cb(err);
}
});
};
Test (running in mocha)
it('Crypto Error: createHash', function () {
const crypto = require('crypto');
const expectedError = new Error('stub error occurred');
let cryptoStub = sinon.stub(crypto, 'createHash').throws(expectedError);
let callback = sinon.spy();
getFileName(null, null, callback);
cryptoStub.restore();
sinon.assert.calledWith(callback, expectedError);
});
I would expect the above test to throw once createHash gets called. If I move the crypto.createHash call outside of the callback (before the pseudoRandomNumber call) it works just fine. I a bit of a newbie so my basic understanding of what sinon and nodejs are doing could be completely wrong. Any help would be much appreciated.

The reason why it seems like createHash() wasn't called was because you were making an assertion before the callback call was complete due to asynchronous function.
Promise with async/await will work. Another method which doesn't involve changing your module to use promise is to do your assertions within the callback.
it('Crypto Error: createHash', function (done) {
const crypto = require('crypto');
const expectedError = new Error('stub error occurred');
let cryptoStub = sinon.stub(crypto, 'createHash').throws(expectedError);
getFileName(null, null, function (err, hash) {
sinon.assert.match(err, expectedError);
cryptoStub.restore();
done();
});
});
This way, you can check that the callback is called with the expected error. One way to confirm this is you can change line 4 to .throws('some other error') and the test will fail.

The problem is that crypto.pseudoRandomBytes() is an async function, so the rest of your test code executes before your callback. That way, your stub is restored before your function is actually used it.
In order to make it properly work, you should update your getFileName.js so it returns a promise - that way you can await it
module.exports = (req, file, cb) => {
return new Promise((resolve, reject) => {
crypto.pseudoRandomBytes(32, (err, raw) => {
try{
cb(err, err ? undefined : crypto.createHash('MD5').update(raw).digest('hex'));
resolve();
} catch(err) {
reject(cb(err));
}
});
});
};
and then in your test
// added async
it('Crypto Error: createHash', async () => {
const crypto = require('crypto');
const expectedError = new Error('stub error occurred');
let cryptoStub = sinon.stub(crypto, 'createHash').throws(expectedError);
let callback = sinon.spy();
await getFileName(null, null, callback);
// once we are here, the callback has already been executed and the promise that getFileName resolved.
cryptoStub.restore();
sinon.assert.calledWith(callback, expectedError);
});

Related

What is the correct way to await fs.readFile from another asynchronous function?

I'm using a combination of Jest and Supertest to test an API endpoint. I'm using the beforeAll() function of Jest to call the endpoint once before a collection of tests. Before I call the endpoint, I'm reading the request body from a file using fs.readFile.
Whatever I try, I cannot seem to await the result of my function that calls fs.readFile. My request is resulting in a 400 response every time, since the function readRequestBody is not being awaited. It seems that the program flow is continuing without awaiting the result and, therefore, sending an empty request body.
Code:
describe("Test POST call " + process.env.ENV, () => {
const url = config.apiURL;
let responseData: request.Response;
beforeAll(async (done) => {
const requestBody = await readRequestBody();
responseData = await request(config.apiURL)
.post("/v1.0/subjects/courses")
.send(requestBody)
.accept("application/vnd.api+json")
.set("content-type", "application/vnd.api+json");
done();
});
test("authorized should return 201 status code", () => {
expect(responseData.status).toBe(201);
});
});
async function readRequestBody() : Promise<string> {
let requestBody: string = "";
fs.readFile("./request.json", "utf8", (err, req) => {
if (err) {
console.log("Error loading request: " + err.message)
}
requestBody = req.replace("{{newCourseUuid}}", uuid.v4());
});
return requestBody;
}
I understand that fs.readFile reads the contents of a file asynchronously, but it looks like I'm not awaiting the results correctly. What am I missing? Is this something related to the fact that beforeAll is, itself, an asynchronous function?
try await fs.promises.readFile('file.txt') instead 👍
https://nodejs.org/api/fs.html#fs_fs_promises_api
async functions implicitly convert their return value to a promise. So your function signature async function readRequestBody() : Promise<string> means readRequestBody will return a Promise to create Promise to read the body. i.e. Promise<Promise<String>>. Instead you need to either remove the async keyword or the Promise from your return value.
Also your implementation of the function is incorrect as it will always return an empty string since fs.readFile is an asynchronous function.
Here is something that might fix both your issues:
function readRequestBody() : Promise<string> {
return new Promise((resolve, reject) => {
fs.readFile("./request.json", "utf8", (err, req) => {
if (err) {
console.log("Error loading request: " + err.message)
reject(err)
}
let requestBody: string = "";
requestBody = req.replace("{{newCourseUuid}}", uuid.v4());
resolve(requestBody)
});
});
}

Stubbing function to return something acceptable for .promise()

I'm running tests and I'm stubbing a function that calls the AWS sqs.deleteMessage function. .promise() is called on the call to this function. Every time I run my tests with the coverage I notice that it jumps to the catch block thus an error must be occurring on my .promise() call.
I've tried stubbing the function to resolve the promise but that doesn't seem to work. I've tried returning data as well and still have the same issue.
Below is an example of the code I'm trying to test. It never reaches the logger.info() line
fooObj.js
const foo = async (req) => {
try{
let res = await bar.deleteMessage(handle).promise();
logger.info("Sqs result message " + JSON.stringify(res));
} catch(error){
#catch block code
}
}
Below is the code for bar.deleteMessage()
bar.js
const aws = require('aws-sdk');
const sqs = new aws.SQS();
deleteMessage = function(handle){
return sqs.deleteMessage({
ReceiptHandle: handle
});
}
And finally here is the test code
const fooObj = require('foo')
const barObj = require('bar')
jest.mock('bar')
describe('foo test', ()=>{
test('a test' , ()=>{
barObj.deleteMessage.mockImplementation(()=>{
return Promise.resolve({status:200})
});
return fooObj.foo(req).then(data=>{
#Expect statements here
})
}
}
So I would like the logger.info line to be reached in coverage but I assume the issue has to do with how I'm stubbing the bar.deleteMessage function. I could use the aws-sdk-mock but I feel like I'm violating unit testing principles by mocking the sqs call that is in another file and the proper way to do it would simply be to properly stub the bar.deletemessage() function
You just need one change:
bar.deleteMessage needs to return an object with a promise property set to the function that returns the Promise:
barObj.deleteMessage.mockImplementation(() => ({
promise: () => Promise.resolve({ status: 200 })
}));
...or you can shorten it to this if you want:
barObj.deleteMessage.mockReturnValue({
promise: () => Promise.resolve({ status: 200 })
});

can await/async makes blocking to non-blocking process in javascript

I read this article from node
https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/
It says the code below is a process blocker:
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
console.log(data);
// moreWork(); will run after console.log
what if I add await?
will the code above becomes non-blocking or it will stay in its true nature?
Example code:
const fs = require('fs');
const data = await fs.readFileSync('/file.md'); // no more blocking
console.log(data);
Thank you
No, the code can't run since await must use in async function.
And await should use for function that return promise.
the code means:
// await new Promise(...)
// console.log(...)
new Promise().then((...) => console.log(...))
If you should non-block function, you should use fs.readFile instead.
Blocking means that the whole application is blocked.
So, all SetInterval, Promise, Events or whatever async callback are paused until that sync function ends its execution.
It's the same of what you get when you use a for..loop.
NodeJS provides you some non-blocking file system methods with callbacks, just change your code like this.
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data)
});
There is the only way.
await operator wait a promise, and wrapped in async function
you should code like this
const fs = require("fs");
function readFile(fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
}
async function f1() {
try {
var x = await readFile("foo.json");
console.log(x);
} catch (e) {
console.log(e); // 30
}
}
f1();

jest everything after expect isn't called

my jest is not working as I expect it. See:
const res = {
send: (content) => {
expect(content).toEqual({
app_status: 501,
errors: {
jwt: {
location: 'body',
param: 'jwt',
value: undefined,
msg: 'The jwt is required'
}
}
});
console.log("after expect");
done();
},
};
Basically EVERYTHING after the expect(content).toEqual ... in res.send is not called. I find that very confusing. I am getting no error except for that my test's are taking too long (because done) is not called and the test is not "closed". So my question is, am I missing something obviously?
The following should work fine. I added asynchronous since send may be called asynchronously:
const createResponse = () => {
var resolve;
const p = new Promise(
(r,reject)=>resolve=r
);
return [
{
send: (value) => {
resolve(value)
}
},
p
];
};
test('(async) fail', done => {
//Router
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
setTimeout(x=>res.send('v1'),10)
});
const request = {
method: 'GET'
};
let [response,p] = createResponse()
router.stack[0].handle(request, response, () => {});
p.then(
x=>expect(x).toBe('v2'),
reject=>expect("should not reject").toBe(reject)
).then(
x=>done()
//,x=>done() //this will cause all tests to pass
);
});
To answer your question in the comment; consider the following code:
const express = require('express');
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
res.send("Hello World");
});
const request = {
method: 'GET'
};
const response = {
send: (value) => {
debugger;//pause here and see the stack
throw new Error("Hello Error.");
}
};
router.stack[0].handle(
request,
response,
//this is the function done in route.js:
// https://github.com/expressjs/express/blob/master/lib/router/route.js#L127
err => {
console.error(err.message);//console errors Hello Error.
}
);
Send throws an error but send is called by your mock which is called by express here. So express catches the exception and then ends up here (done is not from just but your callback).
So it'll call your callback with an error, skipping done from jist and not throwing anything (maybe showing something in log). Since your callback doesn't do anything it times out.
You could try to call done from jist in the callback (at console.error(err.message);).
[UPDATE]
Careful trying to catch the error thrown by expect the following will tell me 1 test passed:
test('(async) fail', done => {
try{
expect(true).toBe(false);
}catch(e){
}
done();
});

Undefined when returning value

I've node project.
Root file is index.js and file helper.js, here I've some helper functions and it imported to index.js.
I'm trying to get some data, using function in helper.js, but when I calling it in index.js it returning undefined.
But in helper.js everething is OK, console.log showing data that I need.
How I can fix this problem?
index.js file content:
const helper = require('./helper');
let data = helper.getData();
console.log(data); // undefined
helper.js file content:
const fs = require('fs');
module.exports = {
getData: () => {
fs.readFile('data.json', 'utf8', (err, data) => {
const allData = JSON.parse(data);
console.log(allData); // IS OK!
return allData;
});
}
}
You can use Promise:
const fs = require('fs');
module.exports = {
getData: () => {
return new Promise(function(resolve, reject){
fs.readFile('data.json', 'utf8', (err, data) => {
if(err){
reject(err);
} else {
try {
resolve(JSON.parse(data));
} catch(ex){
reject(ex);
}
}
});
});
}
}
and then:
helper.getData().then(function(data){
console.log(data);
}, function(err){
// here something failed
});
The problem is that fs.readFile method is asynchronous and will not give you as result any data check the documentation here.
So one option is to use a Promise as I did or to use a callback as suggested in the answer of #Tatsuyuki Ishi, you can check the docs about callback implementation.
The problem is that fs.readFile is an asynchronous function and so doesn't return anything.
If you really need it to return something you can use the synchronous version, fs.readFileSync.
Otherwise - and a better way to do it - would be to have getData return a promise that you can then resolve with allData.
readFile is an asynchronous function, which accepts a callback. You have two options:
1 . Get a callback as parameter in getData().
getData: (callback) => {
fs.readFile('data.json', 'utf8', (err, data) => {
const allData = JSON.parse(data);
console.log(allData); // IS OK!
callback(allData);
});
}
2 . Use the synchronous version.
getData: () => {
var data = fs.readFileSync('data.json', 'utf8');
const allData = JSON.parse(data);
console.log(allData); // IS OK!
return allData;
}
Of course, you can use Promise which is more beautiful on chaining things, but it's often used with dependencies like Bluebird.
The problem is, you are returning allData from the callback function, not the getData function. And since getData has no explicit return, your helper.getData() function will return undefined and this value would printed instead of what you wanted.
I suggest using Promise to return the data properly, as in #sand's answer.

Categories

Resources