Get Nodejs Promise value for use in existing Framework object - javascript

Amateur JavaScript guy here. I've written a private NodeJS module that manages our DB connection strings (Decrypt passwords & connection string construction), but due to the nature of the decrypt, the module returns a promise for the db connection string.
We are using Sails, and the config happens in the export of a variables object:
module.exports.variables = { dbstring: 'mongodb://user:password#host/mydb' }
But now with the promise, it's a little trickier to squeeze a string in here. I've tried putting the 'module.exports.variables' block, inside a '.then' block:
myConfigModule.getDBString('mysql-master').then( result => {
module.exports.variables = { dbstring: result }
}
but then the rest of the sails app fails to start up, with it trying to access variables inside 'module.exports.variables', and only gets 'undefined'. I assume because the rest of the app isn't waiting for the promise to be fulfilled.
Any suggestions?

I'd export a function from your module getDBString, e.g.
Config Module (config.js)
var getDBString = function() {
/* Query DB, returning a promise. */
return queryDB('mysql-master');
}
module.exports = {
getDBString: getDBString
};
Main Module
var config = require('/config.js');
config.getDBString().then((dbString) => {
console.log('DB string: ', dbString)
});

Managed to resolve this, but it's mostly sails.js specific. The sails.lift() method allows you to pass override configs, so in the app.js, I was then able to use my promise to pass the db configs to sails app:
myConfigModule.getDBString('mysql-master').then( result => {
const sailsOverrideSettings.connections = {
someMySQLServer: {
url: result
}
};
sails.lift(sailsOverrideSettings);
})
.catch ( err => {
// Throw error up the stack.
throw (err)
});
Reference:
https://sailsjs.com/documentation/reference/application/sails-lift

Related

How to mock ClientProxy microservice for nestjs in ava test framework

I am trying to give the mock data in ClientProxy but I am keep on receiving null. It could be because of returning Observable. But I cannot figure it out how to properly mock the result.
I am using ava framework to write the tests
test('get user records', async (t) => {
const mockedUserRepository: Repository<UserRecord> =
mock(repository);
const mockedClientProxy = mock(ClientProxy); // mocking nestjs microservice
const userService = new UserService(
instance(mockedUserRepository),
instance(mockedClientProxy),
);
when(mockedClientProxy.send(anything, anything)).thenResolve(
storedUserForTest as any, //this is what I am expecting to be returned a json
);
const result =
await userService.getUser(2);
t.is(result, storedUserForTest); // result returns null
verify(mockedClientProxy.send(anything, anything)).once(); // this is also throwing exception
});
I think I am doing the mocking in a right way but something I am missing out.

how to create instance of class with jest

I am a beginner in writing tests and in jest also. I want to test that this function to be called and return the success promise. I write a unit test for function using jest. The function gets like parameter instance of class that I can't create because its constructor requires parameters that I haven't access. I have a lot of functions that have Session like parametrs. How can test function when you cant provide parametrs for it? Can I mock instance of class or function and handle it without parameter?
async initFlow(session: Session) {
const nextAtomId = session.userInput.getParam('NEXT_ATOM');
if (nextAtomId) {
const nextAtom = await AtomManager.findActiveAtom(nextAtomId);
if (!session.features.useTerms || ['beforeTerms', 'TermsAndConditions'].includes(nextAtom.type)) {
return AtomProcessor.processAtom(session, nextAtom);
}
}
const start = await AtomManager.getStartAtom(`${session.botId}`);
if (!start) {
throw new Error('Could not find start atom');
}
session.user = await UserManager.getGlobalUser(session); // getGlobalUser makes initUser under the hood.
return AtomProcessor.processAtom(session, start);
}
You can mock both AtomManager & UserManager and provide a mock session object when calling initFlow.
jest.mock("./path/to/AtomManager");
jest.mock("./path/to/UserManager");
it("works", async () => {
const mockSession = {
userInput: {
getParam: jest.fn(),
},
botId: "123",
};
const mockUser = "user123";
const mockStartAtom = "atom123";
AtomManager.getStartAtom.mockResolveValue(mockStartAtom);
UserManager.getGlobalUser.mockResolveValue(mockUser);
await initFlow(mockSession);
expect(mockSession.user).toBe(mockUser);
expect(AtomManager.getStartAtom).toHaveBeenCalledTimes(1);
expect(AtomManager.getStartAtom).toHaveBeenCalledWith(mockSession.botId);
expect(UserManager.getGlobalUser).toHaveBeenCalledTimes(1);
expect(UserManager.getGlobalUser).toHaveBeenCalledWith(mockSession);
expect(AtomProcessor.processAtom).toHaveBeenCalledTimes(1);
expect(AtomProcessor.processAtom).toHaveBeenCalledWith(mockSession, mockStartAtom);
});
The snippet above makes the following assertions:
AtomManager.getStartAtom is called once and it's called with the mock botId.
UserManager.getGlobalUser is called once and it's called with the mock session object.
UserManager.getGlobalUser has successfully added the user property on the passed session object.
AtomProcessor.processAtom is called once and it's called with the mock session and the mock start atom.
You can similarly the test other branches of code.

Converting Typescript Code to Javascript Code?

I'm using Shopify's Node Api tutorial to create a Redis store. However, the code block provided is in typescript and my entire project is written in javascript (React/nextjs). I've been working for a few hours to try and convert the code to be useable, but am unable to get it to work properly in my project. Seriously struggling with this.
How would I convert the below code block from typescript to javascript?
/* redis-store.ts */
// Import the Session type from the library, along with the Node redis package, and `promisify` from Node
import {Session} from '#shopify/shopify-api/dist/auth/session';
import redis from 'redis';
import {promisify} from 'util';
class RedisStore {
private client: redis.RedisClient;
private getAsync;
private setAsync;
private delAsync;
constructor() {
// Create a new redis client
this.client = redis.createClient();
// Use Node's `promisify` to have redis return a promise from the client methods
this.getAsync = promisify(this.client.get).bind(this.client);
this.setAsync = promisify(this.client.set).bind(this.client);
this.delAsync = promisify(this.client.del).bind(this.client);
}
/*
The storeCallback takes in the Session, and sets a stringified version of it on the redis store
This callback is used for BOTH saving new Sessions and updating existing Sessions.
If the session can be stored, return true
Otherwise, return false
*/
storeCallback = async (session: Session) => {
try {
// Inside our try, we use the `setAsync` method to save our session.
// This method returns a boolean (true if successful, false if not)
return await this.setAsync(session.id, JSON.stringify(session));
} catch (err) {
// throw errors, and handle them gracefully in your application
throw new Error(err);
}
};
/*
The loadCallback takes in the id, and uses the getAsync method to access the session data
If a stored session exists, it's parsed and returned
Otherwise, return undefined
*/
loadCallback = async (id: string) => {
try {
// Inside our try, we use `getAsync` to access the method by id
// If we receive data back, we parse and return it
// If not, we return `undefined`
let reply = await this.getAsync(id);
if (reply) {
return JSON.parse(reply);
} else {
return undefined;
}
} catch (err) {
throw new Error(err);
}
};
/*
The deleteCallback takes in the id, and uses the redis `del` method to delete it from the store
If the session can be deleted, return true
Otherwise, return false
*/
deleteCallback = async (id: string) => {
try {
// Inside our try, we use the `delAsync` method to delete our session.
// This method returns a boolean (true if successful, false if not)
return await this.delAsync(id);
} catch (err) {
throw new Error(err);
}
};
}
// Export the class
export default RedisStore;
Just save all that typescript code in a .ts file (probably redis-store.ts).
then use typescript compiler to convert to your version of javascript by just running tsc command as below
tsc redis-store.ts
for more compiler options, please visit below
https://www.typescriptlang.org/docs/handbook/compiler-options.html
You basically need to get rid of all the types (Session and string) and switch private to #, maybe something like this:
/* redis-store.js */
import redis from 'redis';
import {promisify} from 'util';
class RedisStore {
#client;
#getAsync;
#setAsync;
#delAsync;
constructor() {
// Create a new redis client
this.client = redis.createClient();
this.getAsync = promisify(this.client.get).bind(this.client);
this.setAsync = promisify(this.client.set).bind(this.client);
this.delAsync = promisify(this.client.del).bind(this.client);
}
storeCallback = async (session) => {
try {
// Inside our try, we use the `setAsync` method to save our session.
// This method returns a boolean (true if successful, false if not)
return await this.setAsync(session.id, JSON.stringify(session));
} catch (err) {
// throw errors, and handle them gracefully in your application
throw new Error(err);
}
};
/*
The loadCallback takes in the id, and uses the getAsync method to access the session data
If a stored session exists, it's parsed and returned
Otherwise, return undefined
*/
loadCallback = async (id) => {
try {
// Inside our try, we use `getAsync` to access the method by id
// If we receive data back, we parse and return it
// If not, we return `undefined`
let reply = await this.getAsync(id);
if (reply) {
return JSON.parse(reply);
} else {
return undefined;
}
} catch (err) {
throw new Error(err);
}
};
/*
The deleteCallback takes in the id, and uses the redis `del` method to delete it from the store
If the session can be deleted, return true
Otherwise, return false
*/
deleteCallback = async (id) => {
try {
// Inside our try, we use the `delAsync` method to delete our session.
// This method returns a boolean (true if successful, false if not)
return await this.delAsync(id);
} catch (err) {
throw new Error(err);
}
};
}
// Export the class
export default RedisStore;

Mocking node_modules which return a function with Jest?

I am writing a typeScript program which hits an external API. In the process of writing tests for this program, I have been unable to correctly mock-out the dependency on the external API in a way that allows me to inspect the values passed to the API itself.
A simplified version of my code that hits the API is as follows:
const api = require("api-name")();
export class DataManager {
setup_api = async () => {
const email = "email#website.ext";
const password = "password";
try {
return api.login(email, password);
} catch (err) {
throw new Error("Failure to log in: " + err);
}
};
My test logic is as follows:
jest.mock("api-name", () => () => {
return {
login: jest.fn().mockImplementation(() => {
return "200 - OK. Log in successful.";
}),
};
});
import { DataManager } from "../../core/dataManager";
const api = require("api-name")();
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("200 - OK. Log in successful.");
expect(api.login).toHaveBeenCalledTimes(1);
});
});
What I find perplexing is that the test assertion which fails is only expect(api.login).toHaveBeenCalledTimes(1). Which means the API is being mocked, but I don't have access to the original mock. I think this is because the opening line of my test logic is replacing login with a NEW jest.fn() when called. Whether or not that's true, I don't know how to prevent it or to get access to the mock function-which I want to do because I am more concerned with the function being called with the correct values than it returning something specific.
I think my difficulty in mocking this library has to do with the way it's imported: const api = require("api-name")(); where I have to include an opening and closing parenthesis after the require statement. But I don't entirely know what that means, or what the implications of it are re:testing.
I came across an answer in this issue thread for ts-jest. Apparently, ts-jest does NOT "hoist" variables which follow the naming pattern mock*, as regular jest does. As a result, when you try to instantiate a named mock variable before using the factory parameter for jest.mock(), you get an error that you cannot access the mock variable before initialization.
Per the previously mentioned thread, the jest.doMock() method works in the same way as jest.mock(), save for the fact that it is not "hoisted" to the top of the file. Thus, you can create variables prior to mocking out the library.
Thus, a working solution is as follows:
const mockLogin = jest.fn().mockImplementation(() => {
return "Mock Login Method Called";
});
jest.doMock("api-name", () => () => {
return {
login: mockLogin,
};
});
import { DataManager } from "../../core/dataManager";
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("Mock Login Method Called");
expect(mockLogin).toHaveBeenCalledWith("email#website.ext", "password");
});
});
Again, this is really only relevant when using ts-jest, as using babel to transform your jest typescript tests WILL support the correct hoisting behavior. This is subject to change in the future, with updates to ts-jest, but the jest.doMock() workaround seems good enough for the time being.

"Cannot find global value 'Promise'" Error in Mocha test

The following has to do with a full stack Node.js problem. It involves Express, Mongoose, and Mocha.
I have a controller module with a function that processes an HTTP call. It basically takes Request and Response objects as its arguments. Within it, it pulls Form data out of the Request object and stores data in multiple MongoDB instances. In order to accomplish multiple data stores we use a call to Promise.all. This is done in an async function. Something like the following
async function saveData(data1 : Data1Interface, data2 : Data2Interface,
res: Response)
{
try
{
//Call 3 save methods each returning promised. Wait fLoginInfoModelor them all to be resolved.
let [data1Handle, data2Handle] = await Promise.all([saveData1(data1),
saveData2(data2)]);
//if we get here all of the promises resolved.
//This data2Handle should be equal to the JSON {"id" : <UUID>}
res.json(data2Handle);
res.status(200);
}
catch(err)
{
console.log("Error saving registration data” + err);
res.json( {
"message" : "Error saving registration data " + err
});
res.status(500);
}
}
Within saveData1 and saveData2 I am doing something like:
function saveData1(data : DataInterface) : Promise<any>
{
let promise = new Promise(function(resolve : bluebird.resolve, reject : bluebird.reject)
{
Data1Model.create(data, function(err,
data){
….
.
.
This works fine! We are doing all of this in Typescript.
However I want to test this method using Mocha. This is where the problems start. For the sake of brevity I am only using one of the Mongoose Models in this example. If I try to run the following code as a mocha unit test I get the following error message. I am not sure what it wants as far as a Promise constructor?
TSError: ⨯ Unable to compile TypeScript
Cannot find global value 'Promise'. (2468)
server/controllers/registration.server.controller.ts (128,17): An async function or method in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your --lib option. (2705)
server/controllers/registration.server.controller.ts (134,66): 'Promise' only refers to a type, but is being used as a value here. (2693)
Note that line 128 is the line that starts “async function saveData(data1 : Data1Interface, data2 : Data2Interface, res: Response)
“
The following two lines
“let [data1Handle, data2Handle] = await Promise.all([saveData1(data1),
saveData2(data2)]); “
and
“ let promise = new Promise(function(resolve : bluebird.resolve, reject : bluebird.reject)”
produce the “'Promise' only refers to a type, but is being used as a value here.” errors.
The Mocha unit test code looks something like the following.
import 'mocha';
import { expect } from 'chai';
import * as sinon from 'sinon';
var config = require('../config/config');
import * as sinonmongoose from 'sinon-mongoose';
import * as controller from './registration.server.controller';
import { Request, Response } from 'express';
import { Data1Interface, Data1Model} from '../models/data1.server.model';
import * as mathUtilities from '../utilities/math.utilities';
import mongoose = require("mongoose");
import * as bluebird from 'mongoose';
(mongoose as any).Promise = bluebird.Promise;
//NOTE: This currently does not work.
describe('Registration related tests', function () {
beforeEach(()=>{
});
afterEach(()=>{
//sinon.restore(authentication);
});
it('should return 200 OK valid outline', function (done) {
let dummyRequest: any = {
body: {
username: "awhelan",
password: "awhelan",
firstName : "Andrew",
lastName: "Whelan",
email: "awhelan#srcinc.com",
source: "none",
school: "Arizona",
consent: true,
dob: "1970-03-10",
gender:"Male",
interviewconsent: true,
recordingconsent: true
}
};
let id = mathUtilities.createId("Andrew", "Whelan", "awhelan#srcinc.com");
let retJson = "{id:" + id +"}";
let dummyResponse: any = {
json: function (data) {
expect(data).to.equal(retJson);
done();
return this;
},
sendStatus: function (code) {
expect(code).to.equal(200);
done();
return this;
}
};
let req: Request = dummyRequest as Request;
let res: Response = dummyResponse as Response;
let mock = sinon.mock(Data1Model).expects('create').yields(null, { nModified: 1 });
controller.register(req, res);
sinon.restore(Data1Model.create);
});
});
Note that the suggestion in ts An async function or method in ES5/ES3 requires the 'Promise' constructor” doesn’t help.
Any suggestions as to how I might move past these errors would be appreciated.
-Andrew
Fixed it by installing es6-shim typings.

Categories

Resources