Mocha Unit Test Case Node JS - javascript

I am facing the problem in mocking function which is being called from other function while writing the unit test cases in Node JS. I am using sinon chai and mocha to write the unit test case in my application. Below is my code
if (req.body.escalationDay === undefined) {
throw new Error('\'escalationDay\' attribute is required');
}
const escalationDay = req.body.escalationDay;
const invoiceId = req.params.id;
const lineItemNumber = req.params.lineItemNumber;
const invoice = await doGetInvoiceById(invoiceId, req);
if (!invoice) {
console.log("hi i am in")
throw new Error(`No invoice found with id ${invoiceId}`);
}
if (!invoice.InvoiceLineItems) {
throw new Error(`No line items found for invoice with id ${invoiceId}`);
}
const lineItem = invoice.InvoiceLineItems.find(li => +li.Id === +lineItemNumber);
if (!lineItem) {
throw new Error(`No line items with line item id ${lineItem.Id} found for invoice with id ${invoiceId}`);
}
}```
and below is the unit test case I am writing for this function
``` it('Should test escalation', () => {
let mockReq = {
params: {
id: '5f6bfc693b2253001a140cba',
lineItemNumber: 1
},
body: { escalationDay: 0 },
query: {
system: "test"
}
};
let sinonStub = sinon.stub(service(reqHandler, microservicesettings), 'doGetInvoiceById');
sinonStub.withArgs('5f6bfc693b2253001a140cba', mockReq).returns(
{ InvoiceLineItems : [] }
);
assert.equal(InvoiceService(reqHandler, microservicesettings).doInvoiceLineItemEscalation(mockReq)).to.be.equal( { InvoiceLineItems : [] });
})```
It will be good if I can get any idea what i am missing

Related

How to initialize App Data in node js and access it without being undefined in jest test?

i am initializing a node js app with crucial data for the app to work from a database in index.js.
index.ts
import {getInitialData} from 'initData.ts';
export let APP_DATA: AppData;
export const initializeAppData = async () => {
try {
APP_DATA = (await getInitialData()) as AppData;
if (process.env.NODE_ENV !== 'test') {
initializeMongoose();
startServer();
}
} catch (error) {
console.log(error);
}
};
initData.ts
let dbName: string = 'initialData';
if (process.env.NODE_ENV === 'test') {
dbName = 'testDb';
}
const uri = `${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`;
export async function getInitialData() {
const client = new MongoClient(uri);
try {
await client.connect();
const database = client.db(dbName);
const configCursor = database
.collection('config')
.find({}, { projection: { _id: 0 } });
const config = await configCursor.toArray();
const aaoCursor = database
.collection('aao')
.find({}, { projection: { _id: 0 } });
const aao = await aaoCursor.toArray();
return { config, aao };
} catch {
(err: Error) => console.log(err);
} finally {
await client.close();
}
}
I'm using this array in another file and import it there.
missionCreateHandler
import { APP_DATA } from '../index';
export const addMissionResources = (
alarmKeyword: AlarmKeyword,
newMission: MissionDocument
) => {
const alarmKeywordObject = APP_DATA?.aao.find(
(el) => Object.keys(el)[0] === alarmKeyword
);
const resourceCommand = Object.values(alarmKeywordObject!);
resourceCommand.forEach((el) => {
Object.entries(el).forEach(([key, value]) => {
for (let ii = 1; ii <= value; ii++) {
newMission.resources?.push({
initialType: key,
status: 'unarranged',
});
}
});
});
};
I'm setting up a mongodb-memory-server in globalSetup.ts for Jest and copy the relevant data to the database from json-files.
globalSetup.ts
export = async function globalSetup() {
const instance = await MongoMemoryServer.create({
instance: { dbName: 'testDb' },
});
const uri = instance.getUri();
(global as any).__MONGOINSTANCE = instance;
process.env.MONGODB_URI = uri.slice(0, uri.lastIndexOf('/'));
process.env.JWT_SECRET = 'testSECRET';
const client = new MongoClient(
`${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`
);
try {
await client.connect();
const database = client.db('testDb');
database.createCollection('aao');
//#ts-ignore
await database.collection('aao').insertMany(aao['default']);
} catch (error) {
console.log(error);
} finally {
await client.close();
}
};
missionCreateHandler.test.ts
test('it adds the correct mission resources to the array', async () => {
const newMission = await Mission.create({
address: {
street: 'test',
houseNr: 23,
},
alarmKeyword: 'R1',
});
const expected = {
initialType: 'rtw',
status: 'unarranged',
};
addMissionResources('R1', newMission);
expect(newMission.resources[0].initialType).toEqual(expected.initialType);
expect(newMission.resources[0].status).toEqual(expected.status);
});
When runing the test, i get an 'TypeError: Cannot convert undefined or null to object at Function.values ()'. So it seems that the APP_DATA object is not set. I checked that the mongodb-memory-server is set up correctly and feed with the needed data.
When i hardcode the content of APP_DATA in index.ts, the test runs without problems.
So my questions are: How is the best practice to set up initial data in a node js app and where to store it (global object, simple variable and import it in the files where needed)? How can the test successfully run, or is my code just untestable?
Thank you!

nodejs TypeError: Cannot read property 'Message' of undefined

I have a lambda function in AWS. I use it to retrieve information from a REST API. When I test it runs returns a 200 status code, but an "ERROR TypeError: Cannot read property 'Message' of undefined
at smsResponder (/var/task/smsResponder.js:33:35)" also shows. I have googled, and tried to use .responseText.
My code is below. Should I be using return or something of the sort?
'use strict'
const AWS = require('aws-sdk')
AWS.config.update({ region: process.env.AWS_REGION || 'us-east-1' })
const { getStock } = require('./getStock')
const KEYWORD = 'stock'
const validateStock = function (elementValue){
let stockTest = AAPL
return stockTest.test(elementValue)
}
const sendSMS = async function (params) {
const pinpoint = new AWS.Pinpoint()
console.log('sendSMS called: ', params)
return new Promise((resolve, reject) => {
pinpoint.sendMessages(params, function(err, data) {
if(err) {
console.error(err)
reject(err)
} else {
console.log("Message sent. Data: ", data)
resolve(data)
}
})
})
}
const smsResponder = async (event) => {
const msg = JSON.parse(event.Sns.Message)
const msgWords = msg.messageBody.split(" ")
// Check the first word of the text message is the keyword
if (msgWords[0].toLowerCase() !== KEYWORD) return console.log('No keyword found - exiting')
// Validate stock name and get price
let message =''
const stockCode = msgWords[1]
if (validateStock(stockCode)) {
message = await getStock(stockCode)
} else {
message = 'Invalid stock symbol - text me in the format "stock stocksymbol".'
}
// Send the SMS response
const params = {
ApplicationId: process.env.ApplicationId,
MessageRequest: {
Addresses: {
[msg.originationNumber]: {
ChannelType: 'SMS'
}
},
MessageConfiguration: {
SMSMessage: {
Body: message,
MessageType: 'PROMOTIONAL',
OriginationNumber: msg.destinationNumber
}
}
}
}
return console.log(await sendSMS(params))
}
module.exports = { smsResponder }
The SNS-Event is differently structured, it should be event.Records[0].Sns.Message .
Here are the docs:
https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html

How to check return value in Jest

I'm doing unit testing with jest and was able to successfully run some of it but there's certain code that I don't know how to test.
I have Create Organization method that needs to check first if the organization is already exist.
async createOrganization(opt) {
try {
const organizationExist = await this.OrganizationRepository.getOne({name: opt.name})
if (organizationExist) {
throw new Error('Organization already exist')
}
} catch (error) {
throw error
}
let organizationObject = {}
organizationObject.name = opt.name
return this.OrganizationRepository.save(organizationObject)
}
and so far this is the unit test code that I was able to cover
describe('Create Organization', () => {
it('should call getOne function', () => {
const mockGetOne = jest.spyOn(OrganizationRepository.prototype, 'getOne')
organizationService.createOrganization(expectedOrganization)
expect(mockGetOne).toHaveBeenCalledWith({name: 'sample org'})
})
it('should return created organization', async () => {
const mockSave = jest.spyOn(OrganizationRepository.prototype, 'save')
mockSave.mockReturnValue(Promise.resolve(expectedOrganization))
const result = await organizationService.createOrganization({name: 'sample org'})
expect(mockSave).toHaveBeenCalledWith({name: 'sample org'})
expect(result).toBe(expectedOrganization)
})
})
now what I want to test is this part
const organizationExist = await this.OrganizationRepository.getOne({name: opt.name})
if (organizationExist) {
throw new Error('Organization already exist')
}
I want to throw an error if the organization is already exist using the name parameter.
Hope you guys can help me. Thanks
you could use toThrowError to test this scenario.
it("Should throw error", async () => {
const mockGetOne = jest.spyOn(OrganizationRepository.prototype, 'getOne')
await organizationService.createOrganization({ name: 'sample org' }); ;
expect(mockGetOne).toHaveBeenCalledWith({ name: 'sample org' });
// Test the exact error message
expect( organizationService.createOrganization({ name: 'sample org' }))
.resolves
.toThrowError(new Error("Organization already exist"));
});
Are you looking for toThrow()?
expect(() => someFunctionCall()).toThrow();

Cannot destructure property `variableName` of 'undefined' or 'null'

The original error say's: Cannot destructure property 'firstime' of 'undefined' or 'null'.
I am developing web-base desktop application for Windows pc using node.js and Electron.
I am trying to persist some data in user data directory, I found the idea and using the same approach in this link.
Writing and fetching data works fine, however the error occurred at the first time of fetching the data.
here is the code for UserPreferences class
const electron = require('electron');
const path = require('path');
const fs = require('fs');
class UserPreferences {
constructor(opts) {
const userDataPath = (electron.app || electron.remote.app).getPath('userData');
this.path = path.join(userDataPath, opts.configName + '.json');
this.data = parseDataFile(this.path, opts.defaults);
console.log(userDataPath);
}
get(key) {
return this.data[key];
}
set(key, val) {
this.data[key] = val;
fs.writeFileSync(this.path, JSON.stringify(this.data));
}
}
function parseDataFile(filePath, defaults) {
try {
return JSON.parse(fs.readFileSync(filePath));
} catch (error) {
return defaults;
}
}
module.exports = UserPreferences;
and here's the function for using the UserPreferences class
function isFirstTime() {
try{
const userAccount = new UserPreferences({
configName: 'fipes-user-preferences', // We'll call our data file 'user-preferences'
defaults: {
user: { firstime: true, accountid: 0, profileid: '' }
}
});
var { firstime, accountid, profileid } = userAccount.get('user');
if (firstime === true) { //check if firstime of running application
//do something
} else {
//do something
}
}catch(err){
console.log(err.message);
}
}
the error occurred on the line where I am checking weather the firstime is true or false.
First of all do not declare a object like var { firstTime, .. } like this. if you do this firstTime will be a property of an anonymous object. That you can never access elsewhere. Check what is the output of userAccount.get('user') function, output contain some object like { firstime: true, accountid: "test", profileid: "test" } then try this. Hope this helps you.
var result=userAccount.get('user');
if(result.firstTime===true){
//your code
}
Here is a version of UserPreferences which will be more natural to use as you write your code. You can create it like you see in isFirstTime.
console.debug(userPreferences[accountId]);
userPreferences[accountId] = 1;
This is preferred because there is no reason for a developer not to treat UserPreferences as an object. Another good idea would be separating the writing to the file into a separate flush method, in case you are updating preferences often.
const electron = require("electron");
const fs = require("fs");
const path = require("path");
class UserPreferences {
constructor(defaultPrefs, pathToPrefs) {
const app = electron.app || electron.remote.app;
this.pathToPrefs = path.join(app.getPath("userData"), pathToPrefs + ".json");
try {
this.store = require(this.pathToPrefs);
}
catch (error) {
this.store = defaultPrefs;
}
return new Proxy(this, {
get(target, property) {
return target.store[property];
},
set(target, property, value) {
target.store[property] = value;
fs.writeFileSync(target.pathToPrefs, JSON.stringify(target.store));
}
});
}
}
module.exports = UserPreferences;
Here is a pure version of isFirstTime, that should do what you want, while maintaining a more robust method of checking for isFirstTime. The check can also be changed so check whether lastSignIn is equal to createdAt (with appropriate defaults, of course).
function isFirstTime() {
const account = new UserPreferences({
user: {
accountId: 0,
createdAt: new Date(),
lastSignIn: null,
profileId: ""
}
}, "fipes-user-preferences");
const {lastSignIn} = account;
return lastSignIn === null;
}

How to make a function that can be called in other functions in Cloud Functions?

I have the following case, when deleting any data, I need to delete the app badges (at the moment I delete them using silent push notication and reduce the app badges number with the cloud function) if the user who sent the request has deleted. But since the user who deleted could send several requests to different users in different places, so I decided that I need to create a function that will be called in firebase database trigger functions and also it will help not to duplicate the same code everywhere .
The function will be approximate such
function adminRemoveAppBadge(userID, dataID, categoryID) {
};
And for example, call it in this function
module.exports = functions.database.ref('/cards/{cardID}/interestedUsers/{interestedUserID}').onWrite(event => {
const currentData = event.data.current;
const prevData = event.data.previous;
const cardID = event.params.cardID;
const interestedUserID = event.params.interestedUserID;
if (currentData.val() && !prevData.val()) {
// value created
return console.log('cardInterestedUserHandler - created');
} else if (!currentData.val() && prevData.val()) {
// value removed
console.log('cardInterestedUserHandler - removed', currentData.val());
const cardRef = admin.database().ref("cards").child(cardID);
const cardRefPromise = cardRef.once("value", function(snap, error) {
if (error) {
return error;
};
if (snap.val()) {
const cardJSON = snap.val();
const cardOwnerID = cardJSON["ownerID"];
if (cardOwnerID) {
const cardOwnerAppBadgesRef = admin.database().ref("userAppBadges").child(cardOwnerID).child("appBadgeModels").orderByChild("dataID").equalTo(cardID);
const cardOwnerAppBadgesRefPromise = cardOwnerAppBadgesRef.once("value", function (cardOwnerAppBadgesRefSnap, error) {
if (error) {
return error;
};
if (cardOwnerAppBadgesRefSnap.val()) {
var deletingPromises = [];
cardOwnerAppBadgesRefSnap.forEach(function(cardOwnerAppBadgesRefSnapChild) {
const appBadgeModelJSON = cardOwnerAppBadgesRefSnapChild.val();
const appBadgeModelID = appBadgeModelJSON["id"];
const senderID = appBadgeModelJSON["senderID"];
if (appBadgeModelID && senderID) {
if (senderID == interestedUserID) {
const cardOwnerAppBadgeRef = admin.database().ref("userAppBadges").child(cardOwnerID).child("appBadgeModels").child(cardOwnerAppBadgeModelID);
const cardOwnerAppBadgeRefPromise = cardOwnerAppBadgeRef.remove();
deletingPromises.push(cardOwnerAppBadgeRefPromise);
// to call
adminRemoveAppBadge
};
} else {
console.log("cardOwnerAppBadgeModelID == null");
};
});
return Promise.all(deletingPromises);
};
});
return Promise.all([cardOwnerAppBadgesRefPromise]);
} else {
return console.log("owner id == null");
};
};
});
return Promise.all([cardRefPromise]);
} else {
return console.log('cardInterestedUserHandler - updated');
};
});
Also functions are in different files. How can I call it in other firebase cloud functions and how do I deploy this function?
Update I tried to do so one of the options as written here and here, but when I tried to do deploy I got an error Cannot find module 'AppBadges/adminRemoveAppBadge.js'.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.adminRemoveAppBadge = function (userID, dataID, categoryID) {
console.log("adminRemoveAppBadge nil");
};
Requested this function so
var adminRemoveAppBadgeModule = require("AppBadges/adminRemoveAppBadge.js");
and call this functions so
adminRemoveAppBadgeModule.adminRemoveAppBadge(cardOwnerID, cardID, 0);
Google Functions are just JS - so normal routes to include code work.
I place my "library" functions in a folder /lib
So my functions folder looks like this:
/functions
/lib
BuildImage.js
SendEmail.js
index.js
package.json
...etc...
within my index.js I just include my code:
const SendMail = require('./lib/SendMail')
const sendMail = new SendMail({
database: db,
mailgun: mailgun
})
exports.sendContactUsMessage = functions.database.ref('/contact-messages/{itemId}').onWrite(sendMail.send(event))
EDIT Added /lib/SendMail.js code:
module.exports = class SendMail {
constructor(config) {
if (!config) {
throw new Error ('config is empty. Must pass database and mailgun settings')
}
if (!config.database) {
throw new Error('config.database is empty. Must pass config.database')
}
if (!config.mailgun) {
throw 'config.mailgun is empty. Must pass config.mailgun'
}
this.database = config.database
this.mailgun = config.mailgun
}
sendEmail (emailData) {
return new Promise((resolve, reject) => {
this.mailgun.messages().send(emailData, (error, body) => {
if (error) {
if (debug) {
console.log(error)
}
reject(error)
} else {
if (debug) {
console.log(body)
}
resolve(body)
}
})
})
}
...
}

Categories