Jest test is failing. Matcher error: received value must have a length property whose value must be a number
Received has type: object
Received has value: {}
but object has a value and it is not {}
index.js file
const fs = require('fs');
select('company')
async function select(selector) {
await fs.readFile('./content.html', 'utf8', function (err, data) {
if (err) throw err;
regexForIds = new RegExp(/<([^\s]+).*?id="company".*?>(.+?)<\/\1>/gi);
matches = data.match(regexForIds);
const obj = {
length: matches.length
};
return obj
});
}
module.exports = select;
index.js file
const select = require('./');
test('select supports ids', () => {
expect(select('#company')).toHaveLength(1);
});
fs.readFile doesn't return a promise, so await fs.readFile won't work.
select is an async function and it doesn't return an obj, so expect(select('#company')).toHaveLength(1) won't work either.
You can fix the first point by wrapping fs.readFile with a Promise (note that there are other ways of fixing this, like using promisify):
const fs = require("fs");
async function select(selector) {
const obj = await new Promise((res, rej) => {
fs.readFile("./content.html", "utf8", function(err, data) {
if (err) rej(err);
regexForIds = new RegExp(/<([^\s]+).*?id="company".*?>(.+?)<\/\1>/gi);
matches = data.match(regexForIds);
const obj = {
length: matches.length,
};
res(obj);
});
});
return obj;
}
module.exports = select;
To fix the second point, you need to change the test slightly, by adding an await before calling select:
const select = require("./");
test("select supports ids", async () => {
expect(await select("#company")).toHaveLength(1);
});
You will probably have to change the location of ./content.html depending on how you run your tests.
Related
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.
This is my controller:
const rssService = require('../services/rss.service');
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res) {
const p = new Promise((resolve, reject) => {
const t = parser();
if (t === undefined) {
resolve(t);
} else {
// eslint-disable-next-line prefer-promise-reject-errors
reject('something bad happened');
}
});
p.then((result) => res.send(result)).catch((message) => console.log(`ERROR ${message}`));
};
module.exports = {
parse,
};
in the function : parser() above, I am trying to call my rss.service.js file which I have the logic. This file is a rss parser which tries to parse the feed and do some calculations (which needs promises and async) and then return the json object.
Here is how my rss.service look :
const rssParser = async function parseRssFeed(url) {
const parser = new Parser();
const appRoot = process.env.PWD;
const downloadDir = `${appRoot}/downloads/`;
if (!fs.existsSync(downloadDir)) {
fs.mkdirSync(downloadDir);
}
try {
const feed = await parser.parseURL('someurl');
const processedFeedItems = await Promise.all(feed.items.map(async (currentItem) => {
const {
fileUrl,
downloadPath,
} = await downloadFile(currentItem);
const hashValue = calculateHash(downloadPath)
return {
title: currentItem.title,
hash: hashValue,
url: mp3FileUrl,
};
}));
return (JSON.stringify(processedFeedItems));
} catch (error) {
console.error(error);
return 'error';
}
};
when I debug my code I can verify that Json object has been created with correct data, but the result does not return to the callee(controller).
I'll go in a little deeper since you mentioned you're new:
const rssService = require('../services/rss.service');
// This is an async function (always returns a promise)
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res, next) {
// In await/async, you should use try/catch/throw instead of .then and .catch
// It does the same thing, but it's the preferred syntax and is "easier" to read IMO
// Out in "the community" people will complain if you mix await/async with promises like that
try {
// Adding await to ensure left-assign works.
// This is necessary because parser is an async function (so returns a promise)
const result = await parser();
// here, since you used `await` you get the value instead of the promise
if (result === undefined) throw new Error('something bad happened')
return res.send(result)
} catch (error) {
console.log(`ERROR ${error.message}`;
// Do you need to do anything else with this error? I assume something like:
return next(error);
}
};
module.exports = {
parse,
};
In a fast look, It seems you have forgot to wait for resolve the parser promise.
...
const p = new Promise(async(resolve, reject) => {
const t = await parser();
...
When i create new variable and assign callback function, But data cannot return from callback function. Undefined is occurring at new variable.
const nedb = require('nedb');
const user = new nedb({ filename: './builds/database/users.db', autoload: true });
var s = user.find({}, function (err,docs) {
if(docs.length == 0) {
var data = false;
} else {
var data = docs;
}
return data;
});
console.log(s);
var s is undefined! ....
You are mixing up callback and Promise which are two different way to handle asynchronous calls.
I recommend you to use of Promises because they are simpler and the present and future of javascript.
Using async/await which is the next step after Promises
const user = {
find: () => ['jon', 'kalessi', 'jorah'],
};
async function getUsers() {
return (await user.find({})) || [];
}
async function myJob() {
const users = await getUsers();
console.log(users);
// Put your next code here
}
myJob();
Full promise :
const user = {
find: () => new Promise((resolve) => resolve(['jon', 'kalessi', 'jorah'])),
};
user.find({})
.then((docs = []) => {
console.log(docs);
// Put you next code here, you can use of the variable docs
})
.catch((err) => {
console.log(err);
});
Full callback :
const user = {
find: (_, callback) => callback(false, ['jon', 'kalessi', 'jorah']),
};
user.find({}, (err, docs = []) => {
if (err) {
console.log(err);
} else {
console.log(docs);
// Put you next code here, you can use of the variable docs
}
});
I think user.find returning the promise. you can do like this.
const nedb = require('nedb');
const user = new nedb({ filename: './builds/database/users.db', autoload: true });
var s = user.find({}, function (err,docs) {
if(docs.length == 0) {
var data = false;
} else {
var data = docs;
}
return data;
});
Promise.all(s)
.then(result => {
console.log(result);
});
Otherwise you can also use await Like this:
async function abc(){
const nedb = require('nedb');
const user = new nedb({ filename: './builds/database/users.db', autoload: true });
var s = await user.find({}, function (err,docs) {
if(docs.length == 0) {
var data = false;
} else {
var data = docs;
}
return data;
});
}
because await worked with async thats why i put it into async function.
I am working on a nodejs code that fetches data from a site, parses it, finds particular data and fetches something else for the data that was previously fetched. But the final return statement is returning without the value fetched from the second API call.
I tried to implement async await, but I am not sure where do I have to put them exactly.
const getMainData = async val => {
let result = [];
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
parseString(xmlData, (err, json) => { //convert xml to json
const { entry } = json.feed; // array of results.
result = entry.map(report => {
const secondInfo = getSomeMoreData(report.something); //axios call
const data = {
id: report.id,
date: report.date,
title: report.title
};
data.info = secondInfo;
return data;
});
});
return { result };
};
I was expecting the function to return the array result that has id, date, title and info. But I am getting info as null since it is going to another function that does one more API call.
Try wrapping parseString in a promise so you can await the result, then make the entry.map callback an async function so that you can use the await keyword to wait for the result of the axios fetch.
async function xml2json(xml) {
return new Promise((resolve, reject) => {
parseString(xml, function (err, json) {
if (err)
reject(err);
else
resolve(json);
});
});
}
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await xml2json(xmlData);
const { entry } = json.feed; // array of results
const result = await Promise.all(
entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); // axios call
const data = {
id: report.id,
date: report.date,
title: report.title,
};
data.info = secondInfo;
return data;
})
)
return { result };
}
Let me know if that works. If not, I can try to help you out further.
The problem with your code is you have mixed promises concept(async/await is a syntactic sugar - so same thing) along with callback concept.
And the return statement is outside callback() of parseString() and the callback would be executed maybe after returning results only because parseString() is an asynchronous function.
So in the following solution I have wrapped parseString() in a promise so that it can be awaited.
const parseStringPromisified = async xmlData => {
return new Promise((resolve, reject) => {
parseString(xmlData, (err, json) => {
if (err) {
reject(err);
}
resolve(json);
});
});
};
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await parseStringPromisified(xmlData);
const { entry } = json.feed; // array of results.
const result = entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); //axios call
return {
id: report.id,
date: report.date,
title: report.title,
info: secondInfo
};
});
return Promises.all(result);
};
I cannot figure out how async/await works. I slightly understand it but I can't make it work.
function loadMonoCounter() {
fs.readFileSync("monolitic.txt", "binary", async function(err, data) {
return await new Buffer( data);
});
}
module.exports.read = function() {
console.log(loadMonoCounter());
};
I know, I could use readFileSync, but if I do, I know I'll never understand async/await and I'll just bury the issue.
Goal: Call loadMonoCounter() and return the content of a file.
That file is incremented every time incrementMonoCounter() is called (every page load). The file contains the dump of a buffer in binary and is stored on a SSD.
No matter what I do, I get an error or undefined in the console.
Since Node v11.0.0 fs promises are available natively without promisify:
const fs = require('fs').promises;
async function loadMonoCounter() {
const data = await fs.readFile("monolitic.txt", "binary");
return Buffer.from(data);
}
To use await/async you need methods that return promises. The core API functions don't do that without wrappers like promisify:
const fs = require('fs');
const util = require('util');
// Convert fs.readFile into Promise version of same
const readFile = util.promisify(fs.readFile);
function getStuff() {
return readFile('test');
}
// Can't use `await` outside of an async function so you need to chain
// with then()
getStuff().then(data => {
console.log(data);
})
As a note, readFileSync does not take a callback, it returns the data or throws an exception. You're not getting the value you want because that function you supply is ignored and you're not capturing the actual return value.
This is TypeScript version of #Joel's answer. It is usable after Node 11.0:
import { promises as fs } from 'fs';
async function loadMonoCounter() {
const data = await fs.readFile('monolitic.txt', 'binary');
return Buffer.from(data);
}
You can use fs.promises available natively since Node v11.0.0
import fs from 'fs';
const readFile = async filePath => {
try {
const data = await fs.promises.readFile(filePath, 'utf8')
return data
}
catch(err) {
console.log(err)
}
}
You can easily wrap the readFile command with a promise like so:
async function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
reject(err);
}
resolve(data);
});
});
}
then use:
await readFile("path/to/file");
From node v14.0.0
const {readFile} = require('fs/promises');
const myFunction = async()=>{
const result = await readFile('monolitic.txt','binary')
console.log(result)
}
myFunction()
To keep it succint and retain all functionality of fs:
const fs = require('fs');
const fsPromises = fs.promises;
async function loadMonoCounter() {
const data = await fsPromises.readFile('monolitic.txt', 'binary');
return new Buffer(data);
}
Importing fs and fs.promises separately will give access to the entire fs API while also keeping it more readable... So that something like the next example is easily accomplished.
// the 'next example'
fsPromises.access('monolitic.txt', fs.constants.R_OK | fs.constants.W_OK)
.then(() => console.log('can access'))
.catch(() => console.error('cannot access'));
There is a fs.readFileSync( path, options ) method, which is synchronous.
const fs = require("fs");
const util = require("util");
const readFile = util.promisify(fs.readFile);
const getContent = async () => {
let my_content;
try {
const { toJSON } = await readFile("credentials.json");
my_content = toJSON();
console.log(my_content);
} catch (e) {
console.log("Error loading client secret file:", e);
}
};
I read file by using the Promise. For me its properly:
const fs = require('fs')
//function which return Promise
const read = (path, type) => new Promise((resolve, reject) => {
fs.readFile(path, type, (err, file) => {
if (err) reject(err)
resolve(file)
})
})
//example how call this function
read('file.txt', 'utf8')
.then((file) => console.log('your file is '+file))
.catch((err) => console.log('error reading file '+err))
//another example how call function inside async
async function func() {
let file = await read('file.txt', 'utf8')
console.log('your file is '+file)
}
You can find my approach below:
First, I required fs as fsBase, then I put the "promises" inside fs variable.
const fsBase = require('fs');
const fs = fsBase.promises
const fn = async () => {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
};
fn();
This produces a String from the contents of your file you dont need to use promises for this to work
const fs = require('fs');
const data = fs.readFileSync("./path/to/file.json", "binary");
see this example
https://www.geeksforgeeks.org/node-js-fs-readfile-method/
// Include fs module
var fs = require('fs');
// Use fs.readFile() method to read the file
fs.readFile('demo.txt', (err, data) => {
console.log(data);
})