Jest mocking implementation of a method accepting callback of third party - javascript

I am using Nest + Cognito to authenticate a user on an application, I have a method inside my Authentication service that I am trying to test/mock which is:
async cognitoRegister(userPool: CognitoUserPool, {
name,
password,
email
}: AuthRegisterInput): Promise < ISignUpResult > {
return new Promise((resolve, reject) => {
return userPool.signUp(
name,
password,
[new CognitoUserAttribute({
Name: 'email',
Value: email
})],
null,
(err, result) => {
if (!result) {
reject(err);
} else {
resolve(result);
}
},
);
});
}
Here the signUp function is a method coming from the third party CognitoUserPool which I managed to mock using module name mapper inside my package.json, here it is:
function CognitoUserPool(data) {
const { UserPoolId, ClientId } = data;
this.userPoolId = UserPoolId;
this.clientId = ClientId;
this.getCurrentUser = jest.fn().mockReturnValue("cognitouserpool");
// This method
this.signUp = jest.fn().mockReturnValue(true);
}
module.exports = CognitoUserPool;
and is implementation:
module.exports = {
CognitoUserPool: jest.fn().mockImplementation(require("./CognitoUserPool")),
};
Since the signUp method accept a callback which is responsible of giving me a result/reject value I should somehow mock it otherwise Jest is giving me a timeout error since the implementation returns a Promise that stays in a pending state.
Basically I am trying to mock a function of this kind:
const foo = (arg1, cb) => {
...do something...
}
const bar = (arg1, arg2...) => {
return new Promise((resolve, reject) => {
return foo(arg1, (err, result) => {
if (!result) {
reject(err)
} else {
resolve(result)
}
})
})
}
Here is what I am trying to do inside my test:
it("should register a cognito user", async () => {
const mockedCongitoUserPool = new CognitoUserPool({
UserPoolId: authConfig.userPoolId,
ClientId: authConfig.clientId,
});
const result = await service.cognitoRegister(mockedCongitoUserPool, {
...mockedUser,
});
console.log(result);
});
I also have a git for that can be helpful:
Main service link
Mocked third party implementation link
Tests implementation link
Any help here is appreciated <3, ask for any further explanation I really need some help on this one.

To have a static resolution, your mocked module should declare an implementation like the following instead of just a return value:
this.signUp = jest.fn().mockImplementation((name, pwd, attlist, something, cb) => {
process.nextTick(cb(null, 'signedup!'))
});
Here process.nextTick just simulates async, but you could just call cb(null, 'some result') as well if you don't care.
If you want to dynamically control the callback resolution, you could override the default mocked implementation depending on your scenario:
let cup: CognitoUserPool;
beforeAll(() => { // or maybe beforeEach, depends on what the mock keeps/does
cup = new CognitoUserPool({ userPoolId: 'fakeUserPoolId', ClientId: 'fakeClientId' });
});
it('should resolve', async () => {
const expected = { any: 'thing' };
cup.signUp.mockImplementation((a, b, c, d, cb) => cb(null, expected));
await expect(service.cognitoRegister(cup, mockedUser)).resolves.toBe(expected);
});
it('should fail', async () => {
const expected = new Error('boom!');
cup.signUp.mockImplementation((a, b, c, d, cb) => cb(expected));
await expect(service.cognitoRegister(cup, mockedUser)).rejects.toThrow(expected);
});

Related

Not able to return authResponse from auth0-js

I'm trying to implement an login mechanize and not able to return a value from the callback function. I'm using this npm package: auth0-js. There's two files in my setup.
The first one is authService.js where I have my login logic:
import auth0 from "auth0-js";
function initializeAuth0Client(domain, redirectUri, clientID) {
return new auth0.WebAuth({
domain: "{YOUR_AUTH0_DOMAIN}",
clientID: "{YOUR_AUTH0_CLIENT_ID}",
});
}
function handleLogin(client, user) {
return client.login(
{
realm,
username,
password,
},
(err, authResult) => {
if (authResult) {
return authResult;
}
}
);
}
module.exports = {
handleLogin,
initializeAuth0Client,
};
The second one: index.js
import { handleLogin, initializeAuth0Client } from "authService";
const auth0Client = initializeAuth0Client(domain, redirectUri, clientID);
const authResponse = handleLogin(auth0Client, user);
console.log(authResponse) // undefined
I tried returning the value from the callback, as well as assigning the result to a local variable inside the function and returning that one, but none of those ways actually return the response. I saw this answer, but it didn't help much.
In the following snippet, both lines will always try to run at the same time.
const authResponse = handleLogin(auth0Client, user);
console.log(authResponse) // undefined
console.log(authResponse) is not going to wait for handleLogin to finish and return authResult
authResult is only available inside of the callback
function handleLogin(client, user) {
return client.login(
{
realm,
username,
password,
},
(err, authResult) => {
if (authResult) {
console.log(authResponse) // works!
return authResult;
}
}
);
}
If you want your code synchronously, or have handleLogin(auth0Client, user); resolve before having the rest of the code run, you can turn handleLogin into a function that returns a Promise that resolves with the authResponse. This will cause console.log(authResponse) to wait for handleLogin(auth0Client, user);.
function handleLogin(client, user) {
return new Promise((resolve, reject) => {
client.login(
{
realm,
username,
password,
},
(err, authResult) => {
if (authResult) {
resolve(authResult);
}
}
}
);
}
const auth0Client = initializeAuth0Client(domain, redirectUri, clientID);
const authResponse = await handleLogin(auth0Client, user);
console.log(authResponse) // works!
If you are doing this in Node, you have to make sure that you call this in an async function. Placing it inside a wrapper function should suffice
async function auth() {
const auth0Client = initializeAuth0Client(domain, redirectUri, clientID);
const authResponse = handleLogin(auth0Client, user);
console.log(authResponse) // works!
}
auth()

testing variable within the function in class using mocha

I need to test value of the url variable in the pullPackage() function in the TASK class.
class TASK {
constructor(taskData, done) {
//some code
}
// Generic Setup
pullPackage() {
return new Promise((resolve, reject) => {
fs.emptydir(this.taskDir, (err) => {
if (err) return reject(err);
const git = require('simple-git')(this.taskDir);
let url = '';
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV === 'test') {
// url = 'ssh://testuser#127.0.0.1:4000/testuser/test-repo-1.git'; // make this match the below format
url = '/git/testuser/test-repo-1';
} else {
const gitAddress = new URL(config.config.GIT_ADDRESS);
url = `${gitAddress.protocol}//runner:${this.taskData.gitJWT}#${gitAddress.hostname}:${gitAddress.port}${this.taskData.repo}.git`;
}
// console.log(url);
// const url = `${gitAddress.protocol}//runner:${this.taskData.gitJWT}#${gitAddress.hostname}:${gitAddress.port}${this.taskData.repo}.git`;
this.logger.log('Cloning from', url);
return git.clone(url, 'repo', (cloneErr) => {
if (cloneErr) return reject(cloneErr);
// console.log(url);
// console.log(resolve);
return resolve(true);
});
});
});
}
}
I'm using Mocha and Chai to do this. I have two test for this function, to check the variable and the promise. The second test runs as expected, but the first always return fails with AssertionError: expected undefined not to be undefined. I think the issue is how I'm accessing the variable during testing. Currently I'm doing it like this: expect(result.url).to.not.be.undefined; Am I going about this correctly?
describe('Test MenloLab Runner - Task Class', () => {
describe('Pull Package', () => {
it('Check URL constant.', () => task.pullPackage().then((result) => {
expect(result.url).to.not.be.undefined; // adjust the access method
}));
it('It should pull package from GIT.', () => task.pullPackage().then((result) => {
expect(result).to.be.true;
}));
});
});
The workaround to check the URL could be done by spying on git.clone method. To do that, we need to use Sinon
I haven't tested with your code but I give you a clue on the solution below:
const sinon = require('sinon');
const git = require('simple-git');
describe('Test MenloLab Runner - Task Class', () => {
describe('Pull Package', () => {
it('Check URL constant.', () => {
return task.pullPackage().then((result) => {
sinon.spy(git, 'clone'); // spying on git.clone method
expect(result.url).to.not.be.undefined; // adjust the access method
const expectedUrl = 'my-expected-not-undefined-url';
sinon.assertCalledWith(git.clone, expectedUrl); // we check whether git.clone is called with not undefined URL
});
});
it('It should pull package from GIT.', () => task.pullPackage().then((result) => {
expect(result).to.be.true;
}));
});
});
Hope it helps

Properly chaining functions in Firebase function

I am building a function in Firebase Cloud Functions, which can utilize Node.js modules.
I am still new to the use of .then() and I'm struggling to figure out a way to chain my 3 functions webhookSend(), emailSendgrid(), and removeSubmissionProcessor() that happen right after the 'count' is incremented (the if statement that checks temp_shouldSendWebhook). The whole idea of returning promises still confuses me a little, especially when it it involves external libraries.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const request = require('request');
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);
const SENDGRID_API_KEY = firebaseConfig.sendgrid.key;
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
exports.submissionProcess = functions.database.ref('/submissions/processor/{submissionId}').onWrite((change, context) => {
var temp_metaSubmissionCount = 0; // omitted part of function correctly sets the count
var temp_shouldSendWebhook = true; // omitted part of function correctly sets the boolean
return admin.database().ref('/submissions/saved/'+'testuser'+'/'+'meta').child('count')
.set(temp_metaSubmissionCount + 1)
.then(() => {
// here is where im stuck
if (temp_shouldSendWebhook) {
webhookSend();
emailSendgrid();
removeSubmissionProcessor();
} else {
emailSendgrid();
removeSubmissionProcessor();
}
})
.catch(() => {
console.error("Error updating count")
});
});
function emailSendgrid() {
const user = 'test#example.com'
const name = 'Test name'
const msg = {
to: user,
from: 'hello#angularfirebase.com',
subject: 'New Follower',
// text: `Hey ${toName}. You have a new follower!!! `,
// html: `<strong>Hey ${toName}. You have a new follower!!!</strong>`,
// custom templates
templateId: 'your-template-id-1234',
substitutionWrappers: ['{{', '}}'],
substitutions: {
name: name
// and other custom properties here
}
};
return sgMail.send(msg)
}
function webhookSend() {
request.post(
{
url: 'URLHERE',
form: {test: "value"}
},
function (err, httpResponse, body) {
console.log('REQUEST RESPONSE', err, body);
}
);
}
function removeSubmissionProcessor() {
admin.database().ref('/submissions/processor').child('submissionkey').remove();
}
I want to be able to construct the 3 functions to be called one after another such that they will all execute.
In order to chain these functions, they each need to return a promise. When they do, you can call them sequentially like this:
return webhookSend()
.then(() => {
return emailSendgrid();
})
.then(() => {
return removeSubmissionProcessor();
});
Or in parallel like this:
return Promise.all([webhookSend, emailSendgrid, removeSubmissionProcessor]);
Now, to make your functions return promises:
emailSendgrid: It looks like this returns a promise (assuming sgMail.send(msg) returns a promise), so you shouldn't need to change this.
removeSubmissionProcessor: This calls a function that returns a promise, but doesn't return that promise. In other words it fires off an async call (admin.database....remove()) but doesn't wait for the response. If you add return before that call, this should work.
webhookSend calls a function that takes a callback, so you'll either need to use fetch (which is promise-based) instead of request, or you'll need to convert it to return a promise in order to chain it:
function webhookSend() {
return new Promise((resolve, reject) => {
request.post(
{
url: 'URLHERE',
form: {test: "value"}
},
function (err, httpResponse, body) {
console.log('REQUEST RESPONSE', err, body);
if (err) {
reject(err);
} else {
resolve(body);
}
}
);
});
}
Use async functions and then you can use .then() or await before every function calls
for reference read this

jest: how to mock a dependency that listens to events?

I came across a very complicated situation. I'll try to keep it as concise as possible.
So I have a code like this in myModule.js:
const lib = require('#third-party/lib');
const myFunction = () => {
const client = lib.createClient('foo');
return new Promise((resolve, reject) => {
client.on('error', (err) => reject(err));
client.on('success', () => {
client.as(param1).post(param2, param3, (err, data) => {
if (err) reject(err);
// Some important logical processing of data
resolve(data);
});
});
});
}
module.exports = { myFunction };
There are a few things I am able to mock, like: createClient.
What I am not able to mock is the event part I don't even know how to do this. And the .as().post() part.
Here's how my jest test looks like:
const myModule = require('./myModule');
const mockData = require('./mockData');
describe('myFunction', () => {
it('Should resolve promise when lib calls success event', async () => {
try {
const myData = await myModule.myFunction();
expect(myData).toMatchObject(mockData.resolvedData);
} catch (err) {
expect(err).toBeNull();
}
})
});
Any help, much Appreciated.
I tried to find similar questions but at this point, my mind has just stopped working...
Please let me know if you need any more details.
Here’s what you need to do:
// EventEmitter is here to rescue you
const events = require("events");
// Mock the third party library
const lib = require("#third-party/lib");
lib.createClient.mockImplementationOnce(params => {
const self = new events.EventEmitter();
self.as = jest.fn().mockImplementation(() => {
// Since we're calling post on the same object.
return self;
});
self.post = jest.fn().mockImplementation((arg1, _cb) => {
// Can have a conditional check for arg 1 if so desird
_cb(null, { data : "foo" });
});
// Finally call the required event with delay.
// Don't know if the delay is necessary or not.
setInterval(() => {
self.emit("success");
}, 10);
return self;
}).mockImplementationOnce(params => {
const self = new events.EventEmitter();
// Can also simulate event based error like so:
setInterval(() => {
self.emit("error", {message: "something went wrong."});
}, 10);
return self;
}).mockImplementationOnce(params => {
const self = new events.EventEmitter();
self.as = jest.fn().mockImplementation(() => {
return self;
});
self.post = jest.fn().mockImplementation((arg1, _cb) => {
// for negative callback in post I did:
_cb({mesage: "Something went wrong"}, null);
});
setInterval(() => {
self.emit("success");
}, 10);
return self;
});
This is only the mock object that you need to put in your test.js file.
Not sure if this code will work as is, although won’t require a lot of debugging.
If you just want to positive scenario, remove the second mockImplementationOnce and replace the first mockImplementationOnce with just mockImplementation.

Return mysql query for graphql resolver function in node-mysql

I'm new to graphql and node so sorry if this is really simple but I'm trying to perform a mysql query so that the query response is returned by graphql for the client to read from. The problem I'm running into is that because node-mysql does query asynchronously, I can't get the query response directly.
After some tinkering I figured it out, new code:
var root = {
login: function({username, password}) {
var s = `select password from users where username='${username}'`;
var result = sql.query(s);
return result.then(response => {
return password == response[0].password
});
}
Here is the function definition for sql.query
exports.query = function(s, callback) {
var promise = new Promise((resolve, reject) => {
con.query(s, function(err, response) {
if (err) throw err;
resolve(response);
});
});
return promise;
Graphql now return that response is not defined.
I personally use mysql, but shouldn't be too much of a difference.
So I do it like this:
exports.getUser = ({ id }) => {
return new Promise((resolve, reject) => {
let sql = `select * from users u where u.user_id = ?`;
sql = mysql.format(sql, [id]);
connection.query(sql, (err, results) => {
if (err) reject(err);
resolve(results);
});
});
};
Wrap the query around a Promise, which resolves when the query is done.
Then in the resolve of a field, you just call .then on the promise and do whatever you want.
const viewerType = new GraphQLObjectType({
name: 'Viewer',
fields: () => ({
user: {
type: userType,
args: {
id: {
type: GraphQLInt,
},
},
resolve: (rootValue, args) => {
return getUser({ id: args.id }).then(value => value[0]);
},
},
}),
});
I appreciate that this is an old post but I've found this topic somewhat confusing and have been investigating different approaches, both which are shown here. One is a bit less verbose then the other (getQuery). I include the code for a mutation which although largely irrelevant to this topic demonstrates how none of this promise complication applies to returning the result of a mutation, just saying.
The documentation describes another approach called promise wrappers https://github.com/sidorares/node-mysql2#using-promise-wrapper. In my case this would have needed a top level await which didn't really fit in with my needs although there are all kinds of workarounds, but I just wanted the simplest possible solution to a simple problem.
resolver.ts
import db from "db";
// this is one way of doing it
const getQuery = async (sql: string) => {
const [rows] = await db.promise().query(sql);
return rows;
};
// and this is another
const getQuery2 = (sql: string) => {
return new Promise((resolve, reject) => {
db.query(sql, (error, results) => {
if (error) return reject(error);
return resolve(results);
});
});
};
export const resolvers = {
Query: {
getUsers: () => getQuery("SELECT * FROM `users`"),
},
Mutation: {
userLogin: (_, { email, password }) => {
return {
__typename: "UserLoginResponse",
status: "success",
jwt: "abc123",
};
},
},
};
db.ts
import mysql2 from "mysql2";
console.log("connecting to RDS db:", process.env.DB_BOOK_IT_HOST);
export const db = mysql2.createConnection({
host: process.env.DB_BOOK_IT_HOST,
user: process.env.DB_BOOK_IT_USER,
password: process.env.DB_BOOK_IT_PASSWORD,
database: process.env.DB_BOOK_IT_DATABASE,
});
export default db;

Categories