I'm writing unit tests for separate middleware functions in Node/Express using Jest.
A simple example of the middleware:
function sendSomeStuff(req, res, next) {
try {
const data = {'some-prop':'some-value'};
res.json(data);
next();
} catch (err) {
next(err);
}
}
And a sample of my test suite:
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
let response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
// How to 'capture' what is sent as JSON in the function?
});
});
});
I have to provide a callback to populate the next parameter, which is called in the function. Normally, this would 'find the next matching pattern', and pass the req and res objects to that middleware. However, how can I do this in a test set-up? I need to verify the JSON from the response.
I don't want to touch the middleware itself, it should be contained in the test environment.
Am I missing something here?
Found a fix!
Leaving this here for someone else who might struggle with the same.
When returning data using res.send(), res.json() or something similar, the response object (from const response = httpMocks.createResponse();)
itself is updated. The data can be collected using res._getData():
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
const response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
});
const { property } = JSON.parse(response._getData());
expect(property).toBe('someValue');
});
});
});
I did a different way by utilising jest.fn(). For example:
if you wanna test res.json({ status: YOUR_RETURNED_STATUS }).status(200);
const res = {};
res.json = jest.fn(resObj => ({
status: jest.fn(status => ({ res: { ...resObj, statusCode: status }
})),
}));
Basically, I mock the res chain methods(json and status).
That way you can do expect(YOUR_TEST_FUNCTION_CALL).toEqual({ res: { status: 'successful', statusCode: 200 }}); if your response structure is like that.
Related
I am trying to use node-http-proxy inside an AdonisJS controller, but I get the error
The "url" argument must be of type string. Received type function
The line causing the error is the proxy.web(request, response, { target: urlToProxy });
async proxy({ request, response }){
var resource = await Resource.query().where('uri', request.url()).with('service.user').with('userSecurity').first()
resource = resource.toJSON()
if(!resource.service.active){
return response.status(404).send(`Parent service '${resource.service.title}' is disabled`)
}
if(!resource.active){
return response.status(404).send(`Resource is disabled`)
}
if(resource.userSecurity.length <= 0) {
return response.status(403).send(`You are not permitted to access that resource. Contact ${resource.service.user.first_name} ${resource.service.user.last_name} (${resource.service.user.email})`)
}
var urlToProxy = url.resolve(resource.service.basepath, request.originalUrl())
var proxy = httpProxy.createProxyServer()
proxy.web(request, response, { target: urlToProxy });
}
In the end I got a bit closer but not a full fix. The getting close bit was to realise the http-proxy was delivering data via buffer so I had to do something like this
proxy.web(req, res, { target: data.service.basepath})
proxy.on('error', function(err, req, res){
console.log("There was an error", err)
})
proxy.on('proxyRes', async (proxyRes, request, response) =>{
var body = new Buffer('')
proxyRes.on('data', (data)=>{
body = Buffer.concat([body, data])
})
proxyRes.on('end', ()=>{
body = body.toString()
try{
res.send(body)
}catch(err){
}
})
});
However, I still could not get it to work as the controller was returning before http-proxy had completed the request.
In the end and probably for the best, I wrote a stand alone proxy app and used the main app just to validate JWT tokens before they go through the Proxy.
You were so close, I wanted to do something similar and wrapped the proxy in a promise so we can wait for the proxy to return before responding with our response:
const proxy = httpProxy.createProxyServer();
const prom = new Promise((resolve, reject) => {
proxy.web(request.request, response.response, {
target: urlToTarget
}, (e) => {
reject(e);
});
proxy.on('proxyRes', function (proxyRes, req, res) {
let body = [];
proxyRes.on('data', function (chunk) {
body.push(chunk);
});
proxyRes.on('end', function () {
body = Buffer.concat(body).toString();
resolve(body);
});
});
});
const result = await prom;
response.body(result);
return response;
I thought I'd give you a complete answer for anyone else that comes across this.
So, i am using jest for testing my node function which is calling fetch() APi to get the data, now when I am writing the test cases for the same i am getting an error like :
expect(received).resolves.toEqual()
Matcher error: received value must be a promise
Received has type: function
Received has value: [Function mockConstructor]
my function :
export function dataHandler (req, res, next) {
const url= "someURL"
if (url ) {
return fetch(url )
.then((response) => response.json())
.then((response) => {
if (response.data) {
console.log(response);
res.redirect(somewhere`);
} else {
throw Error(response.statusText);
}
})
.catch((error) => {
next(error);
});
}
}
testcase :
it('check if fetch returning the response', async () => {
// Setup
const req = jest.fn(),
res = { redirect: jest.fn() },
next = jest.fn();
global.fetch = jest.fn().mockImplementation(() => {
return new Promise((resolve) =>
resolve({
json: () => {
return { data: "hello"};
}
})
);
});
await middlewares.dataHandler(req, res, next);
// Assert
expect(global.fetch).resolves.toEqual({ data: "hello" });
});
Please be advised I am not using any mocking API, and would prefer not to.
Can anyone help me with what's going wrong?
.resolves can only be used with a Promise.
global.fetch is a function so Jest throws an error.
If you are trying to assert that the Promise returned by calling global.fetch resolves to an object with a json function that returns { data: 'hello' } then you can do this:
expect((await global.fetch()).json()).toEqual({ data: 'hello' }); // Success!
...but I suspect that you are really trying to verify that response.data existed and that res.redirect was called with 'somewhere' in which case your assertion should just be this:
expect(res.redirect).toHaveBeenCalledWith('somewhere'); // Success!
I'm working on an Actions on Google chatbot using Dialogflow and Cloud Functions. The runtime is Node.js 6.
Why does this function return the empty string?
function getJSON(url) {
var json = "";
var request = https.get(url, function(response) {
var body = "";
json = 'response';
response.on("data", function(chunk) {
body += chunk;
json = 'chunk';
});
response.on("end", function() {
if (response.statusCode == 200) {
try {
json = 'JSON.parse(body)';
return json;
} catch (error) {
json = 'Error1';
return json;
}
} else {
json = 'Error2';
return json;
}
});
});
return json;
}
This is the intent in which I want to access the json data:
app.intent('test', (conv) => {
conv.user.storage.test = 'no change';
const rp = require("request-promise-native");
var options = {
uri: 'https://teamtreehouse.com/joshtimonen.json',
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
};
rp(options)
.then(function (user) {
conv.user.storage.test = user.name;
})
.catch(function (err) {
conv.user.storage.test = 'fail';
});
conv.ask(conv.user.storage.test);
});
You can try to use the request module for node.js, I tried my self reproducing your use case and worked fine. The code should be something similar to this:
const request = require('request');
request(url, {json: true}, (err, res, body) => {
if (err) { res.send(JSON.stringify({ 'fulfillmentText': "Some error"})); }
console.log(body);
});
Also, you need to add "request": "2.81.0" in the dependencies section inside your package.json file.
The function returns the empty string because https sets up a callback function, but the program flow continues to the return statement before the callback is called.
In general, when working with Dialogflow Intent Handlers, you should be returning a Promise instead of using callbacks or events. Look into using request-promise-native instead.
Two clarifying points:
You must return the Promise. Otherwise Dialogflow will assume the Handler has completed. If you return the Promise, it will wait for the Promise to finish.
Everything you want to send back must be done inside the then() block. This includes setting any response. The then() block runs after the asynchronous operation (the web API call) completes. So this will have the results of the call, and you can return these results in your call to conv.ask().
So it might look something like this:
return rp(options)
.then(function (user) {
conv.add('your name is '+user.name);
})
.catch(function (err) {
conv.add('something went wrong '+err);
});
You can use Axios as well
To install Axios : npm install axios
OR
You can add it in package.json as a dependency
"dependencies": {
"axios": "^0.27.2",
}
index.js
const axios = require('axios').default;
exports.makeRequest = async (req, res) => {
axios.get('https://jsonplaceholder.typicode.com/todos/1')// Dummy URL
.then(function (response) {
// handle success
res.status(200).json({
success:true,
result:response.data
})
})
.catch(function (error) {
// handle error
console.log(error);
})
};
OR
const Axios = require("axios");
exports.makeRequest = async (req, res) => {
const { data } = await
Axios.get('https://jsonplaceholder.typicode.com/todos/1')
res.status(200).send({data})
};
I'm currently learning how to write unit tests in Node.js. For this i have made a small file that can make an API call:
const https = require('https')
module.exports.doARequest = 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) //How can i test if the promise rejects here?
}
resolve(body)
})
})
req.end()
})
}
In order to test the happy flow of this file i have faked a request using nock. However i would like test if JSON.parse throws an error.
For this i think i have to fake the data that's inside Buffer.concat(body).toString(). The fake data should be something JSON.parse cannot parse. That way i can test if the promise gets rejected. Only question is, how do i do this?
Test file corresponding to the doARequest module above:
const chai = require('chai');
const nock = require('nock');
const expect = chai.expect;
const doARequest = require('../doARequest.js');
describe('The setParams function ', function () {
beforeEach(() => {
nock('https://stackoverflow.com').get('/').reply(200, { message: true })
});
it('Goes trough the happy flow', async () => {
return doARequest.doARequest('https://stackoverflow.com/').then((res) => {
expect(res.message).to.be.equal(true)
});
});
it('Rejects when there is an error in JSON.parse', async () => {
//How can i test this part?
});
});
Any help/suggestions will be appreciated.
Right now you are using nock's shorthand for passing back an object, i.e., this line:
nock('https://stackoverflow.com').get('/').reply(200, { message: true });
It's the same as passing back a JSON string, or:
nock('https://stackoverflow.com').get('/').reply(200, JSON.stringify({
message: true
}));
To force JSON.parse to fail, just pass back a string that is not valid JSON, e.g.:
nock('https://stackoverflow.com').get('/').reply(200, 'bad');
I'm trying to grab text from an API that only returns a string of text ((here)) and having troubles throwing that out in a response. When posting, it comes out as [object Response], and the console.log doesn't show the text I want out of it.
The code I'm using:
fetch('http://taskinoz.com/gdq/api').then(
function(response) {
console.log(response);
throttledSendMessage(channel, response);
return response;
})
.catch(function(error) {
throttledSendMessage(channel, "An error has occured");
})
Log can be found here
Thanks for looking with me, couldn't find a solution :/
I think that because fetch returns a Response you need to call one of the functions on Response in order to get at the body's text. Here's an example:
fetch('https://github.com/')
.then(res => res.text())
.then(body => console.log(body));
Probably the problem is in async behavior of node.js. You can read more here
Also, I'm assume you use this package to make fetch request in node.js.
And assume that throttledSendMessage function is synchronous.
About your problem, just try to rewrite co de to use async/await for cleaner solution.
// We'll use IIFE function
(async () => {
const fetchResult = await fetch('http://taskinoz.com/gdq/api')
// Here fetch return the promise, so we need to await it again and parse according to our needs. So, the result code would be this
const data = await fetchResult.text();
throttledSendMessage(channel, data);
return data;
})()
Fetch is not available in nodejs , you could use the node-fetch https://www.npmjs.com/package/node-fetch , or you could use this fetch function:
const https = require('https');
const http = require('http');
function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
if (!url) return reject(new Error('Url is required'));
const { body, method = 'GET', ...restOptions } = options;
const client = url.startsWith('https') ? https : http;
const request = client.request(url, { method, ...restOptions }, (res) => {
let chunks = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
chunks += chunk;
});
res.on('end', () => {
resolve({ statusCode: res.statusCode, body: chunks });
});
});
request.on('error', (err) => {
reject(err);
});
if (body) {
request.setHeader('Content-Length', body.length);
request.write(body);
}
request.end();
});
}
module.exports = fetch;
you could use it like this inside your code :
const result = await fetch(
`https://YOUR_URL`,
{
method: 'PUT',
body: JSON.stringify({ test: 'This is just a test', prio: 1 }),
},
);