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

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.

Related

Error: Collection method aggregate is synchronous

I'm trying the following code:
const Conn = mongoose.createConnection('mongodb://127.0.0.1:27017/db');
const addresses = Conn.collection('users').aggregate([
{
$project: {
_id: false,
ethAddr: true,
}
}
]);
I receive the following error:
[...]\backend\node_modules\mongoose\lib\drivers\node-mongodb-native\collection.js:100
throw new Error('Collection method ' + i + ' is synchronous');
^
Error: Collection method aggregate is synchronous
at NativeCollection.<computed> [as aggregate] ([...]\backend\node_modules\mongoose\lib\drivers\node-mongodb-native\collection.js:100:15)
at file:///[...]/backend/scripts/dbGetMerkleRoot.js:11:46
at file:///[...]/backend/scripts/dbGetMerkleRoot.js:28:3
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)
What in cattle's name I'm doing wrong?
It seems to be a problem with the way mongoose connects to the database. Creating the connection but not connecting prior to invoking the aggregation method causes that exception to be thrown. I should've used it this way:
// create custom connection
const Conn = mongoose.createConnection();
// connect to database
await Conn.openUri('mongodb://127.0.0.1:27017/db');
// #type {AggregationCursor}
const addresses = Conn.collection('users').aggregate([
{
$project: {
_id: false,
ethAddr: true,
}
}
]);
console.log( await addresses.toArray() );
It's frustrating the exception itself is poorly documented.

How to mock module which depends implicitly per test in Jest?

I have an integration test where I make actual DB calls to the MongoDB database. But in order to test whether the transaction is expired or not, I need to mock the DB for that particular test. There are many reasons for me to make the actual DB call, I'm mentioning the state just for the sake of this example.
Jest has jest.doMock function but that is helpful only when I wanted to import the function within the test but in my case, It's the DB function which I wanted to mock for that particular test when is getting called inside the express middleware.
There is another option to mock the entire ../db module but that will complicate the tests a lot in my actual project. It would be very easy for me if I can mock the DB call for a specific test and for rest all the tests it should make the real DB calls.
Is there a way to do it in Jest?
// a.ts
import express from "express"
import db from "../db";
const app = express()
app.get("/api/deduct-balance/:txn_id", (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
});
// a.test.ts
import db from "../db";
describe("mixed tests", () => {
test("should make REAL db calls", async () => {
await axios.get("/api/deduct-balance/123")
const txn = await db.findById("123");
expect(txn.state).toBe("DEDUCTED");
});
test("should use MOCKED value", async () => {
// need a way to mock the DB call so that I can return an expired transaction
// when I hit the API
const { data } = await axios.get("/api/deduct-balance/123")
expect(data).toBe({
error: {
message: "txn expired"
}
});
});
})
Integration tests are overkill for this scenario. Simple unit tests would suffice. They are fast to execute, test exactly one thing and you should have lots of them.
Because you're defining the handler as an anonymous function it's hard to unit test by default. So the first action is to make it easier to test by extracting it.
// deduct-balance-handlers.ts
export const deductBalanceByTransaction = async (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
}
It will also makes the app configuration more clean.
// a.ts
import express from "express"
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
const app = express()
app.get("/api/deduct-balance/:txn_id", deductBalanceByTransaction);
Now it's easy to reuse the handler in your test without relying on the web framework or database.
// a.test.ts
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
jest.mock('../db');
describe("deduct-balance", () => {
test("Expired transaction should respond with 401 status", async () => {
const response = mockResponse();
deductBalanceByTransaction(request, response);
expect(response.status).toBe(401);
});
})
For simplicity's sake I left the part of creating a mock response and mocking the module out of the code. More can be learned about mocking here: https://jestjs.io/docs/en/manual-mocks

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.

Jest mockedCoeus.mockImplementation throws a TypeError

I'm using Jest to write a test and mock a function that calls an HTTP request.
import { mocked } from "ts-jest/utils";
import * as pull from "../src/pull";
import fs = require("fs");
// read the reponse data from a file.
const response = JSON.parse(
fs.readFileSync("./__fixtures__/pr.json", "utf8")
);
// have jest mock the function and set it's response.
jest.mock("../src/pull");
const mockedCoeus = mocked(pull.getPullRequest, true);
mockedCoeus.mockImplementation(async () => {
return response as any;
});
// write the test.
describe("#get details for a PR", () => {
it("should load user data", async () => {
const data = await pull.getPullRequest(165, "data-ios");
expect(data).toBeDefined();
expect(data.updated_at).toEqual("2020-04-10T16:46:30Z");
});
});
The test passes, however, I get the following error when running npm jest
TypeError: mockedCoeus.mockImplementation is not a function
I've looked at other reported errors having to do with the placement of jest.mock however, it does not seem to be the case here. Why is this error thrown but the tests pass? How can I fix it?

Get Nodejs Promise value for use in existing Framework object

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

Categories

Resources