I have a function that calls https.get inside a promise which I want to test with Jest.
The function is like this:
const request = (url) => {
return new Promise((resolve, reject) => {
const chunks = [];
https.get(url, (stream) => {
stream
.on('data', (chunk) => {
if( chunk ) {
chunks.push(JSON.parse(chunk));
}
})
.on('error', (err) => {
reject(err);
})
.on('end', () => {
const data = doSomething(chunks);
resolve(data)
});
});
})
}
I want to test that when the function resolves on "end" and rejects on "error";
Currently I have a test like this but because .on("end") doesn't get called, the promise never resolves.
describe("request", () => {
it("Should resolve", async (done) => {
const response = await request("my-url");
expect(response).toEqual("some-data")
})
})
How can I mock events like .on("end") to be called and ensure the promise resolves?
You can do something like this.
// ./request.test.js
jest.mock('https', () => ({
methodToMock: {}
}));
const Stream = require('stream');
const request = require("./request");
const httpsMock = require("https");
describe("request", () => {
it("Should resolve", async () => {
var streamStream = new Stream()
httpsMock.get = jest.fn().mockImplementation((url, cb) => {
cb(streamStream)
streamStream.emit('data', 'some');
streamStream.emit('data', '-');
streamStream.emit('data', 'data');
streamStream.emit('end'); // this will trigger the promise resolve
})
const response = await request("my-url");
expect(response).toEqual("some-data");
})
})
const https = require("https");
const request = (url) => {
return new Promise((resolve, reject) => {
const chunks = [];
https.get(url, (stream) => {
stream
.on('data', (chunk) => {
if (chunk) {
// chunks.push(JSON.parse(chunk));
chunks.push(chunk);
}
})
.on('error', (err) => {
reject(err);
})
.on('end', () => {
// const data = doSomething(chunks);
const data = chunks.join('');
resolve(data)
});
});
})
}
module.exports = request;
Note that jest.mock('https', ...) need to be called before const request = require("./request"); if you want https to be mocked.
Related
I am attempting to retrieve the JSON data stored in the following API:
https://api.hatchways.io/assessment/blog/posts
Using node.js and https requests, I constantly receive an array of [ Promise { }, Promise { } ]. Unfortunately, I can only search by one tag at a time, and I have to retrieve a list of posts that has at least one tag from a list of provided tags, before sorting the posts. My code is below:
const express = require('express');
const app = express();
app.get("/api/ping", (req, res) => {
res.status(200).send("{\"success\": true}");
})
app.get("/api/posts", (req, res) => {
const tags = req.query.tags;
const sortBy = req.query.sortBy;
const direction = req.query.direction;
if (!tags) res.status(400).send("Must provide at least one tag.");
let tag_array = tags.split(',');
let posts_array = [];
tag_array.forEach(tag => {
let posts = getPost(tag);
posts_array.push(posts);
})
console.log(posts_array);
})
app.listen(3000, () => console.log("Listening on port 3000..."));
function getPost(tag) {
const https = require('https');
return new Promise( (resolve, reject) => {
const options = {
hostname: 'api.hatchways.io',
path: `/assessment/blog/posts?tag=${tag}`
}
let body = [];
const req = https.request(options, res => {
res.on('data', data => {
body.push(data);
});
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch (error) {
reject(error);
}
resolve(body);
});
});
req.on('error', error => {
reject(error);
});
req.end();
}).then(function(data) { return data; }, function(error) { console.log(error) });
}
the getPost method is returning a promise, just do this:
app.get("/api/posts", async (req, res) => {
const tags = req.query.tags;
const sortBy = req.query.sortBy;
const direction = req.query.direction;
if (!tags) res.status(400).send("Must provide at least one tag.");
let tag_array = tags.split(',');
const promises = [];
tag_array.forEach(tag => {
promises.push(getPost(tag))
});
posts_array = await Promise.all(promises)
console.log(posts_array)
})
Just wait for all promises to resolve using the await keyword.
app.get("/api/posts", async (req, res) => { // Add the async keyword
//... Skipped some code for concision
tag_array.forEach(tag => {
let posts = getPost(tag);
posts_array.push(posts);
})
await Promise.all(posts_array) // Add this to await all promises to resolve
console.log(posts_array);
})
I am playing with promises and modified a script from Medium.
When I run the script it prompts for the code then displays the json data before I can input a value. I then input a value without the prompt for the script to exit.
How can I get the input before the API call works?
'use strict'
const request = require('request')
const readline = require('readline')
let userDetails
const getInput = prompt => new Promise( resolve => {
const io = { input: process.stdin, output: process.stdout }
const read = readline.createInterface(io)
read.question(`${prompt}: `, data => {
console.log(data)
read.close()
resolve(data)
})
})
const getData = () => new Promise( (resolve, reject) => {
const options = {
url: 'https://api.github.com/users/marktyers',
headers: {
'User-Agent': 'request'
}
}
// Do async job
request.get(options, (err, resp, body) => {
if (err) reject(err)
else resolve(JSON.parse(body))
})
})
function main() {
const GitHubAPICall = getData()
const getBase = getInput('input base currency')
GitHubAPICall
.then(result => {
userDetails = result
console.log('Initialized user details')
console.log(userDetails)
}).then(getBase)
.catch(err => console.log(err))
}
main()
In your main function, you can do it like that:
function main() {
const GitHubAPICall = getData; // WITHOUT ()
const getBase = getInput; // Those 2 declarations are useless, btw
GitHubAPICall()
.then(result => {
userDetails = result
console.log('Initialized user details')
console.log(userDetails)
})
.then(() => getBase())
.then(data => // Do something with the data returned by 'getInput')
.catch(err => console.log(err))
}
I am newbie on node and sinon and I'm having trouble to test the component below. I would like to check if the res.status and res.send were called inside the component.
Component to be tested
module.exports = {
handle: function(promise, res, next, okHttpStatus) {
promise
.then(payload => res.status(okHttpStatus ? okHttpStatus : 200).send(payload))
.catch(exception => next(exception));
}
};
Unit test
const sinon = require("sinon");
const routerPromiseHandler =
require("../../../main/node/handler/PromiseHandler");
describe("Should handle promisse", () => {
it("should handle success promise return", () => {
const successMessage = {message: "Success"};
const promiseTest = new Promise((resolve, reject) => {
resolve(successMessage);
});
let res = {
status: function() {},
send: function() {}
};
const mockRes = sinon.mock(res);
const expectStatus = mockRes.expects("status").withExactArgs(200).atLeast(1)
const expectSend = mockRes.expects("send").withExactArgs(successMessage).atLeast(1)
const spyNext = sinon.spy();
routerPromiseHandler.handle(promiseTest, res, spyNext, 200);
expectStatus.verify();
expectSend.verify();
});
});
I managed to solve the problem. The sinon check wasn't work because the spys were been called inside a promise. To check if the spy were been called. I had to add the assertions inside the then and catch of the promises.
const sinon = require("sinon");
const { mockResponse } = require("mock-req-res");
const routerPromiseHandler = require("../../../main/node/handler/PromiseHandler");
describe("Should handle promisse", () => {
it("should handle success promise return", () => {
const successMessage = { message: "Success" };
const promiseTest = new Promise((resolve, reject) => {
resolve(successMessage);
});
const mockedRes = mockResponse();
const spyNext = {};
routerPromiseHandler.handle(promiseTest, mockedRes, spyNext, 200);
promiseTest.then(() => {
sinon.assert.calledWithMatch(mockedRes.status, 200);
sinon.assert.calledWithMatch(mockedRes.send, successMessage);
})
});
it("should handle error promise return", () => {
const errorMessage = { error: "error" };
const promiseError = new Promise((resolve, reject) => {
reject(errorMessage);
});
const mockedRes = mockResponse();
const nextSpy = sinon.spy();
routerPromiseHandler.handle(promiseError, mockedRes, nextSpy, 200);
promiseError
.then(() => {
// Promise always need the then
})
.catch(exception => {
sinon.assert.calledWithMatch(nextSpy, errorMessage);
})
});
});
I have a function that I use for HTTP requests:
export default {
getRequest: (url, params) => {
return new Promise((resolve, reject) => {
superagent.get(url)
.query(params)
.set('Accept', 'application/json')
.end((err, response) => {
if (!response) {
reject(new Error('Something went wrong...'));
return;
}
const payload = response.body || response.text;
if (err) {
reject(payload || err);
return;
}
resolve(payload);
});
});
}
};
I want to test this function when Promise resolves or rejects.
My test looks like this:
import superagent from 'superagent';
import HTTPAsync from '../HTTPAsync';
describe('HTTPAsync. test', () => {
describe('getRequest test', () => {
const url = '/url';
const params = { param: 'value' };
const result = HTTPAsync.getRequest(url, params);
it('Should be promise', () => {
expect(result).toBeInstanceOf(Promise);
});
it('Should be pfromise', () => {
expect(result.resolve()).toBe('');
});
});
});
But I dont know how to resolve returned promise in happy scenario or error and check results of function
I made another type of test, and that helped me:
import superagent from 'superagent';
import HTTPAsync from '../HTTPAsync';
describe('HTTPAsync. test', () => {
describe('getRequest test', () => {
superagent.get = jest.fn(url => {
return superagent;
});
superagent.query = jest.fn(query => {
return superagent;
});
superagent.set = jest.fn(args => {
return superagent;
});
superagent.end = jest.fn(cb => cb(null, { text: 'zaebis voda' }));
const url = 'localhost:5000/url';
const params = { param: 'value' };
const result = HTTPAsync.getRequest(url, params);
it('Should be promise', () => {
expect(result).toBeInstanceOf(Promise);
// HERE I WILL CHECK EXPECTED VALUES
return result.then(data => console.log(data));
});
});
});
What you could do is catch you request on sucess or Error, using superagent like this :
const request = require('superagent');
describe('HTTPAsync. test', () => {
it('Should be pfromise', done => {
request
.get('/your_url')
.then(res => {
// res.body, res.headers, res.status
//your own logic
done();
})
.catch(err => {
// err.message, err.response
//your own logic
done();
});
});
});
My function is
exports.downloadFromBucket = function(fileKey) {
const localPath = `${process.cwd()}/data/${fileKey}`
return new Promise((resolve, reject) => {
const localFile = fs.createWriteStream(localPath)
const awsStream = s3.getObject({
Bucket: process.env.UPLOAD_BUCKET,
Key: fileKey
})
.createReadStream()
.on('error', (err) => {
logger.info('Error downloading file', err)
return reject(err)
})
.on('finish', () => {
logger.info('Completed downloading')
return resolve(localPath)
})
.pipe(localFile)
})
}
How would I go about writing a unit test for this using mocha and sinon?
This may not be the prettiest solution but assuming you want to mock s3 and fs and test the on('error') and on('finish') behavior:
You could use a custom s3 mocking class, stub the original s3 and fs with sinon and trigger the events you want to test.
// Custom S3 Mocking Library
class S3MockLibrary {
constructor() {
this.events = {};
}
getObject(options) {
return this;
}
createReadStream() {
return this;
}
on(event, func) {
this.events[event] = func;
return this;
}
pipe(file) {
return this;
}
emit(event, err) {
this.events[event](err);
}
}
Test on('finish')
it('should verify', async () => {
const s3Mock = new S3MockLibrary();
const fsStub = sinon.stub(fs, 'createWriteStream').returns('success');
const s3Stub = sinon.stub(s3, 'getObject').returns(s3Mock);
// Emit the finish event async
setTimeout(() => {
s3Mock.emit('finish');
}, 0);
const result = await downloadFromBucket('test');
fsStub.restore();
s3Stub.restore();
sinon.assert.calledOnce(fsStub);
sinon.assert.calledOnce(s3Stub);
assert.equal(result, `${process.cwd()}/data/test`);
});
Test on('error)
it('should fail', async () => {
const s3Mock = new S3MockLibrary();
const fsStub = sinon.stub(fs, 'createWriteStream').returns('success');
const s3Stub = sinon.stub(s3, 'getObject').returns(s3Mock);
setTimeout(() => {
s3Mock.emit('error', 'testError');
}, 0);
let error;
await downloadFromBucket('test').catch((err) => {
error = err;
});
fsStub.restore();
s3Stub.restore();
sinon.assert.calledOnce(fsStub);
sinon.assert.calledOnce(s3Stub);
assert.equal(error, 'testError');
});