Jest test a promise wrapped request - javascript

I have a request call wrapped in a promise chain as follows
import * as request from 'request';
public async getData (): Promise<string> {
const token = await this.util.getToken();
const options = {
withCredentials: true,
Connection: 'keep-alive'
};
return new Promise<string>((resolve, reject) => {
request(this._Host, options, (err, data) => {
if (err != null) {
return reject('Fetch Failed');
}
const values = JSON.parse(data.body);
this._result = values.result;
return resolve('Fetch Success');
});
});
}
And I'm using jest to test this method using the following code
it('should fetch data', async(done) => {
const classobj = new ClassName(config);
(classobj as any).util.getToken.// tslint:disable-line
mockResolvedValue('some-token');
const spy = jest.mock('request', (path, options) => {
return Promise.resolve('Fetch Success');
});
const dispatcher = await classobj.getData().then(done());
});
However the code inside request block never gets triggered. But the test is displayed as running successfully as well. I also noticed that if I pass a values that's already resolved within the return new Promise then the code block is getting triggered without any issues. Is there something that's missing in this code to test the request call?

Related

Get Promise resolve from separate callback

I am sending data to a Bluetooth device, and the responses are handled by a listener that's set up during the connection process:
device.connect().then(device => {
device.registerResponseListener((data) => {
// handle response
}
}
I have a separate function that sends data to the device:
const sendData = (device, data) => {
device.write(data);
}
My question is, how can I Promisify this code? I'd like to be able to do
const sendData = (device, data) => {
return new Promise((resolve, reject) => {
device.write(data);
// resolve...?
});
}
But how do I get the resolve into the Bluetooth response listener?
I don't know what API you're using but you can try BluetoothRemoteGATTCharacteristic API. It has writeValueWithResponse method which return Promise.
https://developer.mozilla.org/en-US/docs/Web/API/BluetoothRemoteGATTCharacteristic
If I understood you correctly then you can do it like this
const sendData = async (device, data) => {
const response = await device.write(data);
await Promise.all(device.responseListeners.map(listener => listener(response)))
}
The best possible solution in this case, while still not ideal, was to store the resolve function in variable at a higher scope:
var sendDataResolve;
device.connect().then(device => {
device.registerResponseListener((data) => {
sendDataResolve(data);
}
}
const sendData = (device, data) => {
return new Promise((resolve, reject) => {
sendDataResolve = resolve;
device.write(data);
});
}
...
sendData(device, "data")
.then(result => {
console.log("Got result",result);
});
The caveat is that Promise resolutions are NOT guaranteed to be tied correctly to the original request. This ONLY works with one request at a time.

How to make HTTP request with promises and retries in Node.js

I am trying to write a few wrappers around Node HTTP/S module requests, without using axios. node-fetch or any other 3rd party module.
For example, I want to have functions sendGet, sendPost, sendJSON, sendFile etc. In ideal case, these functions will be implementing core functionmakeRequest, just with different parameters.
I want each wrapper to return a promise, so the caller can do anything with the result.
I also want the wrapper to have an argument, how many times will be request retried in case of failure.
So idea is something like this. So far I am able to make a wrapper and pass promise. But I am unable to add ability to retry on failure. It should be (in ideal scenario), part of makeRequest function, but I was unable to to do so, when combined with promises. Thank you for your ideas
// intended usage of wrapper
sendGet('http://example.com', 3).then().catch()
// wrapper applies makeRequest fnc with various arguments
const sendGet = (url, retries) => {
return makeRequest('GET', url, retries)
}
const sendPost = (url, retries) => {
return makeRequest('POST', url, retries)
}
// core function
const makeRequest = async (method, url, retries = 0) => {
// returns reject on bad status
// returns resolve with body on successful request
return new Promise((resolve, reject) => {
const options = {/*method, hostname, etc */};
const request = http.request(options, (response) => {
let chunks = [];
if (response.statusCode < 200 || response.statusCode >= 300) {
return reject('bad status code')
}
// collect data
response.on('data', (chunk => {
chunks.push(chunk)
}))
// resolve on end of request
response.on('end', () => {
let body = Buffer.concat(chunks).toString();
return resolve(body)
})
})
request.end();
})
}
Try this, the original function now is called tryRequest and outside there is the for loop to do the retries
// core function
const makeRequest = async (method, url, retries = 0) => {
const tryRequest = async () => {
// returns reject on bad status
// returns resolve with body on successful request
return new Promise((resolve, reject) => {
const options = {method, };
const request = http.request(url, options, (response) => {
let chunks = [];
if (response.statusCode < 200 || response.statusCode >= 300) {
return reject('bad status code')
}
// collect data
response.on('data', (chunk => {
chunks.push(chunk)
}))
// resolve on end of request
response.on('end', () => {
let body = Buffer.concat(chunks).toString();
return resolve(body)
})
})
// reject on error of request (Service down)
request.on('error', function (error) {
reject(error);
})
request.end();
})
}
for (let i=1; i<=retries; i++) {
try {
console.log('Try No.', i);
url = url.substring(0, url.length - 1); // TODO To test, delete after it
return await tryRequest();
} catch(error) {
if (i < retries) continue;
throw error;
}
}
}
Test
await sendGet('http://example.com/abc', 3); // Will work at the 3th try retry
await sendGet('http://example.xyz/abc', 3); // Will fail server not found
You can use a custom promise & axios or any other promise-based request:
(Live Demo)
import { CPromise } from "c-promise2";
import cpAxios from "cp-axios";
const url =
"https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s";
(async()=>{
const response= await CPromise.retry(() => cpAxios(url).timeout(5000));
})();
More complex:
import { CPromise } from "c-promise2";
import cpAxios from "cp-axios";
const url =
"https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s";
const promise = CPromise.retry(
(attempt) => {
console.log(`Attempt [${attempt}]`);
return cpAxios(url).timeout(attempt * 1000 + 500);
},
{ retries: 3, delay: (attempt) => attempt * 1000 }
).then(
(response) => console.log(`Response: ${JSON.stringify(response.data)}`),
(err) => console.warn(`Fail: ${err}`)
);
// promise.pause()
// promise.resume()
// promise.cancel()

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

Node.js - Mock result of a promise

I want to mock the result of a function within a node module so that i can run assertions.
Considering the following node module:
const doPostRequest = require('./doPostRequest.js').doPostRequest;
const normalizeSucessResult = require('./normalizer.js').normalizeSucessResult;
const normalizeErrorResult = require('./normalizer.js').normalizeErrorResult;
exports.doPost = (params, postData) => {
return doPostRequest(params, postData).then((res) => {
const normalizedSuccessResult = normalizeSucessResult(res);
return normalizedSuccessResult;
}).catch((err) => {
const normalizedErrorResult = normalizeErrorResult(err);
return normalizedErrorResult;
})
}
The function doPostRequest returns a promise. How can i fake the return value of this promise so that i can assert if normalizeSucessResult has been called?
So for i have tried:
const normalizeSucessResult = require('./normalizer.js');
const doPostRequest = require('./doPostRequests.js');
const doPost = require('./doPost.js');
it('runs a happy flow scenario', async () => {
let normalizeSucessResultStub = sinon.stub(normalizeSucessResult, 'normalizeSucessResult');
let postData = { body: 'Lorum ipsum' };
let params = { host: 'someUrl', port: 433, method: 'POST', path: '/' };
sinon.stub(doPostRequest, 'doPostRequest').resolves("some response data"); //Fake response from doPostRequest
return doPost.doPost(params, postData).then((res) => { //res should be equal to some response data
expect(normalizeSucessResultStub).to.have.been.calledOnce;
expect(normalizeSucessResultStub).to.have.been.with("some response data");
});
});
The doPostRequest module looks like this:
const https = require('https')
module.exports.doPostRequest = function (params, postData) {
return new Promise((resolve, reject) => {
const req = https.request(params, (res) => {
let body = []
res.on('data', (chunk) => {
body.push(chunk)
})
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString())
} catch (e) {
reject(e)
}
resolve(body)
})
})
req.on('error', (err) => {
reject(err)
})
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
You can use Promise.resolve to return a promise with any given value.
Promise.resolve(“hello world”);
For stub your func you need to do like this
sinon.stub({doPostRequest}, 'doPostRequest').resolves("some response data")
Okay, i figured it out. The function doPostRequest was loaded using require, on the top of the file using const doPostRequest = require('./doPostRequest.js').doPostRequest;
In order to mock the data that comes back from a function that is loaded using require i had to use a node module called mock-require. There are more modules that can take care of this (proxyquire is a populair one) but i picked mock-require (i did not have a specific reason for choosing mock-require).
For anyone else that is stuck with a similar problem, try mock-require to mock the respose from files that are loaded using require.

Testing HttpClient call with callFake()

I am trying to create a spec to test a method in my Angular service that makes a GET request. The difficulty I am having is mocking the method to get it to return an error instead of the response. If I cannot get it to return an error (such a 400 or 500 for example) I cannot provide full code coverage...
Code being tested:
maingrid.service.ts:
async loadAccountListPromise(id: string) {
let queryParams = `?emailAddress=${id}`;
let promise = new Promise((resolve, reject) => {
this.http.get(`${this.baseUrl}` + queryParams, { responseType: 'json' })
.toPromise()
.then(
(data) => {
this.results = this.formatData(data);
resolve(this.results);
},
(err) => {
this.logService.error('loadAccountListPromise() exception:', err);
this.setError(this.message[0], err);
reject('loadAccountListPromise() exception');
}
);
});
return promise;
}
setError(errorMessage: string, errorCode?: string): void {
this._error.next(new NxpError(errorMessage, 'AccountListService',
errorCode));
}
clearError(): void {
this._error.next(null);
}
This is the spec I have attempted to write to mock the method using callFake():
maingrid.service.spec.ts
it('logs and sets a local error for system errors/exceptions', () => {
let id: string = 'ppandya#pershing.com';
let myUrl = 'https://localhost:9999/...';
let queryParams = `?emailAddress=${id}`;
spyOn(httpClient, 'get').and.callFake( loadAccountListPromise( (response) => {
// need to return error here...somehow
}));
spyOn(logService, 'error');
spyOn(maingridService, 'setError');
maingridService.loadAccountListPromise(id);
let request = httpMock.expectOne(myUrl + queryParams);
expect(request.request.method).toEqual('GET');
httpMock.verify();
expect(logService.error).toHaveBeenCalled();
expect(maingridService.setError).toHaveBeenCalled();
});
I am not sure what I need to do to properly mock the loadAcountListPromise() method so that it enters the error block and calls the setError() and logService.error() methods.
Try to use the 'spyOn()' and return a throw like this:
spyOn(httpClient, 'get').and.returnValue(Observable.throw({status: 404}));
//Observable.throw(new Error(`Error: ${error}`));

Categories