Mocking http requests in node using mocha and sinon - node.js

I have written a NodeJS app using express that proxies some calls to external APIs. So I am trying to write a unit test using Mocha and Sinon. My goal is to test the app without any internet connectivity so I am trying to mock the https requests and return mock replies.
I'm having a problem that I can't find any examples or tutorials that fit my case. My node app listens on port 8081 for http requests and then proxies them to another site. I want to test my app without it having to actually send the request to those external servers. I'm trying it below and I put the json replies I want to send back in the server.respondsWith() function.
Am I doing this the right way by making an ajax call with chai? or should I be sending the requests inside my app somehow. Any help is appreciated.
var assert = require('assert');
var chai = require('chai');
var spies = require('chai-spies');
var chaiHttp = require('chai-http');
var https = require('https');
var should = chai.should();
var expect = chai.expect;
var sinon = require('sinon');
chai.use(spies);
chai.use(chaiHttp);
describe('Car Repository', function() {
var server;
before(function() {
server = sinon.fakeServer.create();
});
after(function() {
server.restore();
});
var url = 'http://127.0.0.1:8081';
it('should succeed and return a list of cars', function(done) {
server.respondWith('POST', 'https://api.sandbox.cars.com/v2/token_endpoint', JSON.stringify({"access_token":"1t3E4IykfpJAbuFsdfM2oFAo5raB5vhfOV0hAYe","token_type":"bearer","expires_in":604800}));
server.respondWith('GET', url+'/cars', JSON.stringify({'test':'this works'}));
chai.request(url)
.get('/cars')
.end(function(err, res) {
if (err) {
throw err;
}
res.should.have.status(200);
res.body.should.have.property('test');
console.log(res.body);
done();
});
});
});

Check out the Nock library. It does exactly what you're looking for.
Nock is an HTTP mocking and expectations library for Node.js
Nock can be used to test modules that perform HTTP requests in isolation.
For instance, if a module performs HTTP requests to a CouchDB server or makes HTTP requests to the Amazon API, you can test that module in isolation.

The new solution here is sinon's fake server:
http://sinonjs.org/releases/v2.1.0/fake-xhr-and-server/#fake-server

Take a look at node-tdd and the useNock flag. It builds on top of mocha and nock (mentioned in the accepted answer) and automatically creates and uses a recording file for each test.
We love that it's so easy to use. Basically just "enable and forget" and focus on writing requests / test cases. If requests for a test change, one still needs to delete or adjust the recording file, but at least it's entirely separate from the code.

Related

How to stub AWSXray.captureHTTPsGlobal using Sinon?

In my nodejs lambda function I use AWSXray to simply capture https calls made by the function.
const AWSXRay = require("aws-xray-sdk-core");
AWSXRay.captureHTTPsGlobal(require("https"));
In my unit tests how do I mock this?
I have tried mocking it with sinon by:
before(async () =>
await sandbox.stub(AWSXRay, 'captureHTTPsGlobal').returns({})
);
after(async () => {
await sandbox.restore();
});
Getting error in test as:
OperationalError: Failed to get the current sub/segment from the context.
at Object.contextMissingRuntimeError [as contextMissing]
The problem was not with Xray, but the way I was writing the tests.
AWSXray will try to capture all https calls. But this scenario should ideally not arise in tests because in an ideal test suite all http/https calls anyway should be stubbed.
I solved the problem by properly mocking the units where https calls were being made.

How to test http request on Dialogflow Fulfillment with the Inline Editor

I am testing Dialogflow Fulfillment with the Inline Editor.
What I am trying to do is a http request using que 'request' library.
Here is the code I am using:
const requesthttp = require('request');
requesthttp('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY', { json: true }, (err, res, body) => {
if (err) { return console.log(err); }
console.log(body.url);
console.log(body.explanation);
});
But It returns me an error of not found.
I also noticed an alert on my Dialogflow with the following message:
"Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions."
So... Probably I can't test this piece of code without configuring a billing account.
My question is... Is there a url that I can use to test this code?
Or the only way for me to test this code is configuring a billing account and paying for it?
Thanks in advance
There are a number of approaches to testing your code.
If you want to continue to use Dialogflow's Inline Editor, you will need to setup Firebase to use a payment plan. However, the Blaze plan is "pay as you go" after a basic level of use. This level of use should be sufficient to cover most testing (and even very light production) uses of the service without imposing a charge. Once your Action has been approved, you're able to receive credits for the Google Cloud Platform, which can be applied to this use in case you go over the minimum level.
You can also use Firebase Cloud Functions, which the Inline Editor is based on, and your own local editor. One advantage of this is that you can serve the function locally, which has many of the same features as deploying it, but doesn't have the URL restriction (it is your own machine, after all). You can use a tool such as ngrok to create a secure tunnel to your your machine during testing. Once you have tested, you can deploy this to Firebase with a paid plan.
You can, of course, choose to use any other hosting method you wish. Google and Dialogflow allow you to run your fulfillment webhook on any server, as long as that server can provide an HTTPS connection using a valid, non-self-signed, certificate. If you're using node.js, you can continue to use these libraries. If you wish to use another language, you will need to be able to parse and return JSON, but otherwise you have no restrictions.
There are a lot of ways to create your own server like using NodeJS client with Express.JS which you can expose to the internet using NGROK as webhook for fulfilment.
Develop a webhook. You can use different client libraries in NodeJS (AoG Client or Dialogflow Client) or in Python (Flask-Assistant or Dialogflow Client) or can create your own just using JSON request/response with Dialogflow and Action-on-Google.
Once the webhook is ready, run it locally and expose to the internet using NGROK.
Start with following code for Actions-on-Google with Express.JS
'use strict';
const {dialogflow} = require('actions-on-google');
const express = require('express');
const bodyParser = require('body-parser');
const app = dialogflow();
app.intent('Default Welcome Intent', conv => {
conv.ask('Hi, Welcome to Assistant by Express JS ');
});
express().use(bodyParser.json(), app).listen(8080);
Since DF uses firebase cloud functions you can use https as in nodejs. But requesting domains outside of the google/firebase universe will require the paid version of firebase.
const https = require('https');
return new Promise((resolve, reject) => {
const hostname = info.hostname;
const pathname = info.pathname;
let data = '';
const request = https.get(`https://${hostname}${pathname}`, (res) => {
res.on('data', (d) => {
data += d;
});
res.on('end', resolve);
});
request.on('error', reject);
});

Verify hapi server.log is being called with expected message

I have a requirement to verify that certain actions are being logged to the server and would like to know why I'm having trouble stubbing the call to hapi server.log().
In my unit tests I instantiate a hapi server using:
const Hapi = require('hapi');
const server = new Hapi.Server();
I have my stub setup as:
const Sinon = require('sinon');
const logStub = Sinon.stub(server, 'log');
I am using server.inject to call the endpoint that I expect to call server.log. When I debug the test, I have a breakpoint set to verify that server.log is being called in my endpoint and I have a breakpoint in my unit test to verify that the logStub is being called. However, when I call this:
server.inject(localRequest).then(() => {
const spyCall = logStub.getCall(0);
})
spyCall is null and if I inspect logStub, called is false and the callCount is 0, despite server.log being called in my endpoint.
What am I missing here? Does calling server.inject cause an issue with stubbing methods on the hapi server?
The best solution I have been able to find is to use the server.once method on the hapi server.
I neglected to mention in my question that I'm using hapi version 16.6.2 so this solution may not work for the latest version of hapi as it is much different.
server.once({
name: 'log',
filter: 'myTag',
}, (update) => {
Code.expect(update.data).to.equal('expectedMessage');
});
Using this approach allowed me to specify the event 'log' and the specific tag (or tags) for my log event. Hope this helps someone else!

Node.JS express server creation methods difference

I have recently inherited a project of a Node.JS and Express based API, and I have noticed express server creation is as such (simplified version):
// http is required.
var http = require('http');
var express = require('express');
var app = express();
// Note http is used to create server, and app is used as param:
http.createServer(app).listen(3000, function (request, response) {
console.log('listening on port 3000');
});
Everything works as expected of course.
I have been trying to figure out what exactly is happening behind the scenes here, mostly in comparison to the method in Express API, which shows:
// http is not required.
var express = require('express');
var app = express();
// Note Express is used to create the server:
var server = app.listen(3000, function () {
console.log('listening on port 3000');
})
Note the difference in server creation using http, and using Express directly.
Is there any benefit in using a specific one of the two method? What is the actual difference between the two?
Micro-optimization-wise, is it preferred to avoid requiring 'http', which is probably required by express anyway?
Thanks from ahead!
Both are more or less functionally equivalent, in the second example the express constructor returns a new object which effectively wraps up the http.createServer call internally (i.e. when you call app.listen).
If you are going to use express then you should use it's recommended APIs, the first approach is considered outdated.

Node.js Express block

My Problem is, that I'm planning to use express to cache all requests which I receiver for a certain amount of time until I send all responses at once.
But unfortunately I can't receive a second request until I've responded to the first one. So I guess node / express is somehow blocking the further processing of other requests.
I build a minimal working example for you, so you can see better what I'm talking about.
var express = require('express');
var app = express();
var ca = [];
app.get('/hello.txt', function(req, res){
ca.push(res);
console.log("Push");
});
setInterval(function(){
while (ca.length) {
var res = ca.shift();
res.send('Hello World');
console.log("Send");
}
},9000);
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
When I'm sending just one request to localhost:3000 and wait for 9sec I'm able to send a second one. But when I send both without waiting for the callback of the interval, the second one is blocked until the first interval triggered.
Long Story short: Why is this blocking happening and what ways are there to avoid this blocking.
PS: It seems that the default http package shows another behavior http://blog.nemikor.com/2010/05/21/long-polling-in-nodejs/
try it with firefox and chrome to prevent serializing the requests...
OK, I've got the solution.
The issue wasn't in my code, it was caused by Chrome. It seems that Chrome is serializing all requests, which target the same URL. But nevertheless it sends both request and won't serve the second request with the response of the first.
Anyway, thanks for you help!

Resources