Node.js - How to test HTTPS requests with promise - javascript

I started writing unit tests for my nodeJs application so i could learn about this concept.
After writing some basic tests for simple functions (using Mocha and Chai) i want to move on to some more complex tests.
I have written a simple piece of code that can make a request using node's HTTPS module. That code looks like this:
const https = require('https')
module.exports.doRequest = 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()
})
}
Now i want to invoke this method with the following parameters:
const PARAMS = {
host: 'jsonplaceholder.typicode.com',
port: 433,
method: 'GET',
path: `/todos/1`,
headers: {
Authorization: 'Bearer 123'
}
}
And make the request like so:
getTodos = (PARAMS) => {
return doRequest(PARAMS).then((result) => {
if (result.errors) { throw result }
return {
'statusCode': 200,
'body': JSON.stringify({ message: result.title }),
}
}).catch((error) => ({
'statusCode': error.statusCode,
'body': JSON.stringify({ message: error.message }),
}
))
}
Now my question is how i can test this bit of code properly. I have looked on how to tackle this with the Nock.js libary but i don't have a good understanding on where to start.
If anyone can point me in the right direction on how to start with writing some tests for this bit of code i will be thankfull.

In general, you would want to black box your HTTP handling, so that as few modules in your application need to care about the details of HTTP as possible.
In the source folder, you'd have one module (e.g. commonhttp.js). You want this to export your HTTP functions, and other modules in your application use them like this:
const commonhttp = require('./commonhttp');
commonhttp.doRequest( ... ).then( ... );
Other modules, like todos.js, and various other modules, will export their own functions using that module, for example:
const commonhttp = require('./commonhttp');
const todos = {
getTodos( ... ) {
return commonhttp.doRequest( ... );
},
createTodo( ... ) {
return commonhttp.doRequest( ... );
},
// etc.
};
module.exports = todos;
For your unit tests, when you test the todos.js module, you want to mock any calls to the commonhttp module; you can use simple mocha + Sinon for this, and spy on the doRequest method. Basically all you're testing is "when I call getTodos, I expect it to make a call to doRequest with these arguments". You'd follow this pattern for all the modules in your application that uses doRequest.
You also, of course, want to test the commonhttp module -- that spec is where Nock might come in handy. It's not strictly necessary, you can also "block-box" the http module, but you have to set up a lot of complicated spies to mimic the behavior of http; instead, writing a spec (using Nock) that says "ok, I call doRequest with these params, that should have made this HTTP call" does make sense.

Related

Cypress: remove all cookies with intercepted route

I'm intercepting my login and logout routes in my functional tests with Cypress. (I have to stub them because the Magic technology I'm using for authentication does NOT support a test mode for the server side SDK, yet.)
Here is the code for the routes:
import {
loginRoute,
logoutRoute,
} from 'features/user-authentication/user-authentication-api';
// ...
cy.intercept(loginRoute, request => {
request.reply({
headers: {
'Set-Cookie': `magic-auth-token=${Cypress.env(
'validMagicAuthToken',
)}`,
},
statusCode: 200,
body: { success: true },
});
});
cy.intercept(logoutRoute, request => {
request.reply({
headers: {
'Set-Cookie': `magic-auth-token=; Max-Age=-1; Path=/`,
},
statusCode: 302,
});
});
I'm mimicking the original route's behavior, where they add and remove cookies. The login route's stub works perfectly. However, the stub for the login route does not.
The original logout route looks like this:
import { parse, serialize } from 'cookie';
// ...
function removeTokenCookie<T>(response: NextApiResponse<T>) {
const cookie = serialize(TOKEN_NAME, '', {
maxAge: -1,
path: '/',
});
response.setHeader('Set-Cookie', cookie);
}
const logoutHandler: NextApiHandler = async (request, response) => {
const session = await getSession(request);
if (session) {
await magic.users.logoutByIssuer(session.issuer);
}
removeTokenCookie(response);
response.writeHead(302, { Location: '/' });
response.end();
};
How can I remove the cookies using the logout route's stub? For some reason the cookie does NOT get removed when I set the headers as I did above.
Cypress has the clearCookie command, but it can't be used inside the intercept callback.
cy.intercept(logoutRoute, request => {
cy.clearCookie('magic-auth-token')
request.reply...
})
This is the error
CypressError
Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.
The cy command you invoked inside the promise was: cy.clearCookie()
Looking at the source code for clearCookie, it boils down to the internal command
Cypress.automation('clear:cookie', { name: <cookie-name> })
While it's an internal command, it's use has been demo'd here Cypress Automation and here Testing an Application in Offline Network Mode
The type definitions were added recently Add type for Cypress.automation #7573
Here's a proof of concept,
it('clears cookies in intercept', () => {
cy.setCookie('magic-auth-token', '1234')
cy.getCookies().should('have.length', 1)
cy.intercept('*', (req) => {
Cypress.automation('clear:cookie', { name: 'magic-auth-token' })
})
cy.visit('http://example.com').then(() => {
// after the request has been intercepted
cy.getCookies().should('have.length', 0)
})
})

node.js timeout restarts api call

Platform:
I have an api in sails.js and a frontend in react. The calls between front and back end are being made with fetch api.
More information:
In the course of some api endpoints I have to execute an external file, at this point I am using the execfile() function of node.js, and I have to wait for it to be executed to respond to the frontend.
What is the problem?
If the file is executed in a short time, for example less than 1 minute everything runs well and the behavior occurs as expected on both sides, but if (in this case) the file takes more than 1 minute to execute, there is something to trigger a second call to api (I do not know where this is being called, but I tested the same endpoint with postman and I did not have this problem so I suspect the react / fetch-api) and the api call with the same data from the first call is redone. This causes the file to run twice.
Something that is even stranger is that if you have the DevTools Network inspector turned on this second call does not appear, but nothing in the documentation of sailjs points to this behavior.
Example of an endpoint in sails.js:
/**
* FooController
*/
const execFile = require("child_process").execFile;
module.exports = {
foo: async (req, res) => {
let result = await module.exports._executeScript(req.body).catch(() => {
res.status(500).json({ error: "something has occurred" });
});
res.json(result);
},
_executeScript: body => {
return new Promise((resolve, reject) => {
let args = [process.cwd() + "/scripts/" + "myExternalFile.js", body];
let elem = await module.exports
._execScript(args)
.catch(err => reject(err));
resolve(elem);
});
},
_execScript: args => {
return new Promise((resolve, reject) => {
try {
execFile("node", args, { timeout: 150000 }, (error, stdout, stderr) => {
if (error || (stderr != null && stderr !== "")) {
console.error(error);
} else {
console.log(stdout);
}
let output = { stdout: stdout, stderr: stderr };
resolve(output);
});
} catch (err) {
reject(err);
}
});
}
};
Example of component react with fetch call:
import React from "react";
import { notification } from "antd";
import "./../App.css";
import Oauth from "./../helper/Oauth";
import Config from "./../config.json";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
syncInAction: false,
data: null
};
}
componentDidMount() {
this.handleSync();
}
async handleSync() {
let response = await fetch(Config.apiLink + "/foo/foo", {
method: "POST",
mode: "cors",
headers: {
Authorization: Oauth.isLoggedIn(),
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(this.state.myData)
}).catch(err => {
notification.error({
key: "catch-ApiFail",
message: "Erro"
});
});
let json = await response.json();
this.setState({
syncInAction: false,
data: json
});
}
render() {
return <div>{this.state.data}</div>;
}
}
export default App;
What is my expected goal / behavior:
It does not matter if the call takes 1 minute or 10 hours, the file can only be called once and when it finishes, then yes, it can return to the frontend.
Note that the examples do not have the original code and have not been tested. Is a simplified version of the code to explain the behavior
I ended up solving the problem, apparently nodejs has a default timing of 2 minutes on the server, and can be rewritten to miss this timout or increase it.
This was just adding a line of code at the beginning of the foo() endpoint and the problem was solved.
The behavior of redoing the call is that it is not documented, and it is strange not to have this behavior when using the postman, but here is the solution for whoever needs it.
Final result:
foo: async (req, res) => {
req.setTimeout(0);
let result = await module.exports._executeScript(req.body).catch(() => {
res.status(500).json({ error: "something has occurred" });
});
res.json(result);
};

JavaScript single request to two different ports

I'm testing a script where one function makes an HTTP request and is then called in another function.
The first function is:
export function getFeedData (sub) {
if (getFeedId(sub) === 2) {
return axios.get('http://localhost:4000').then((data) => JSON.parse(data));
}
}
And the second is:
export function isDelay (sub, stop) {
return getFeedData(sub).then((data) => {
return data.entity.filter((entityObj) => {
return entityObj.stop_time_update !== undefined;
});
}).then((newData) => {
console.log(newData);
}).catch((err) => {
console.log(err);
});
}
The reason they're two different functions is that the second will eventually be longer, and I wanted to separate everything out for the sake of simplicity and making my code a bit more declarative.
The tests for these functions currently look like this:
import express from 'express';
import { getFeedId, getFeedData, reverseStop, isDelay } from '../mocks/apiMock';
const app = express();
app.use(express.static('../mocks/MockData.json'));
it('returns json data', (done) => {
app.listen(4000, function () {
expect.assertions(2);
return getFeedData('L').then((data) => {
expect(data).toBeDefined();
expect(data.header.gtfs_realtime_version).toBe('1.0');
});
});
done();
});
it('returns either the delay or time until the next train' , (done) => {
app.listen(4000, function () {
isDelay('L', 'Lorimer St');
});
done();
});
That second test doesn't run because it's trying to listen on a port that's already occupied.
The solution I had in mind would be to pass app.listen() 0 as its first parameter so it listens on a random port. However, I don't know how I could get my axios request to request that specific port. Is there a way to do this? Or perhaps a better solution to my problem? Please be kind as this is my first real independent dive into creating node/express servers, and I'm trying my best to research problems on my own before posting here.
You should only use one specific HTTP server port, e.g :4000.
Then, you should setup your HTTP server like this:
const port = 4000;
let server
beforeAll((done) => {
server = app.listen(port, () => {
done();
})
})
afterAll((done) => {
server && server.close(done)
})
// Your test suites and test cases.
Then you can test your functions and they make HTTP requests to the same endpoint, e.g. http://localhost:4000.

Mock Remote HTTP Response in Tests

I'm attempting to write a test that checks the behaviour of my app depending on the response received from an API. To do this, I'm trying to make the response to the request I make have the config that I need.
An example is checking to see that my app redirects to the home page after logging in. I need the response to be HTTP 200 and have any value for an API key.
I am using axios to make the requests
Currently I have tried the following libraries with no success
moxios
axios-mock-adapter
nock
Does anyone have any experience with mocking remote HTTP requests and their responses?
EDIT: If it helps, I am using Mocha and Chai for tests
it('Does stuff', function () {
moxios.stubRequest('/auth/get_api_key', {
status: 200,
responseText: 'hello'
})
return this.app.client.setValue('[name="username"]', 'testing').then(() => {
return this.app.client.setValue('[name="password"]', 'testing').then(() => {
return this.app.client.submitForm('#login-form').then(() => {
return this.app.client.getRenderProcessLogs().then(function (logs) {
console.log(logs)
})
})
})
})
})
The code above is what I'm using to see if the request goes through and it outputs this
[ { level: 'SEVERE',
message: 'http://127.0.0.1:8000/auth/get_api_key/ - Failed to load resource: net::ERR_CONNECTION_REFUSED',
source: 'network',
timestamp: 1510082077495 } ]
My example was mostly taken from the moxios docs.
First in your test import the libraries you need. Afterwards, you'll handle creating and destroying the mock axios server in your beforeEach and afterEach hooks. You'll then pass a sinon.spy() into axios' get request you want to spy on.
This get request will be mocked by moxios and you can access it through moxios.wait(() => { moxios.request.mostRecent(); }); Inside the wait() method you can set the response you want on the most recent requests.
Afterwards, just like when you use axios you'll use the then() function to get your response. Inside the then() you can create your tests for that specific response. When you're done with your specs call done(); to close the request and move the specs along.
import axios from 'axios';
import moxios from 'moxios';
import sinon from 'sinon';
import { equal } from 'assert'; //use the testing framework of your choice
beforeEach(() => {
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('Does stuff', (done) => {
moxios.withMock(() => {
let onFulfilled = sinon.spy();
axios.get('/auth/get_api_key').then(onFulfilled);
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: {
apiKey: 1234567890
}
}).then((res) => {
equal(onFulfilled.called, true);
equal(res.status, 200);
equal(res.response.apiKey, 1234567890);
done();
})
})
})
});

How to stub https.request response.pipe with sinon.js?

Let's say, that I have this simple code:
var https = require('https');
var options = {
host: 'openshift.redhat.com',
port: 443,
path: '/broker/rest/api',
method: 'GET'
};
var req = https.request(options, function(response) {
console.log(response.statusCode);
response.pipe(save stream to file with fs)
});
req.on('error', function(e) {
console.error(e);
});
req.end();
Well, I'm bit new with sinon.js and I'd like to ask: How to stub response.pipe()?
Of course, I can make stub for https.request and return somethin with .on and .end and thats easy, but I have no idea, how to test if response.pipe() was called with proper arguments... (nodejs documentation says that response is callback)
Documentation is not helpful in this case!
ofc testing env is mocha, and can use chai too
Please give me some advices or examples.
Thanks, Matt
I wrapped your code into a function that accepts a callback because in current implementation we don't actually know when the piping is actually finished. So assuming we have a function like this:
const downloadToFile = function (options, callback) {
let req = https.request(options, function (err, stream) {
let writeStream = fs.createWriteStream('./output.json');
stream.pipe(writeStream);
//Notify that the content was successfully writtent into a file
stream.on('end', () => callback(null));
//Notify the caller that error happened.
stream.on('error', err => callback(err));
});
req.end();
};
There are 3 problems to solve:
As response is a readable stream. We want to mock the data it emits.
We want to mock .pipe method check if we are piping to the right stream.
We also need to mock https.request method not to make actual call
Here is how we can achieve this:
const {PassThrough} = require('stream');
describe('#downloadToFile', () => {
it('should save the data to output.json', function (callback) {
const mockResponse = `{"data": 123}`;
//Using a built-in PassThrough stream to emit needed data.
const mockStream = new PassThrough();
mockStream.push(mockResponse);
mockStream.end(); //Mark that we pushed all the data.
//Patch the 'https' module not to make an actual call
//but to return our stream instead
sinon.stub(https, 'request', function (options, callback) {
callback(null, mockStream);
return {end: sinon.stub()}; //Stub end method btw
});
//Finally keep track of how 'pipe' is going to be called
sinon.spy(mockStream, 'pipe');
downloadToFile({url: 'http://google.com'}, (err) => {
//Here you have the full control over what's happened
sinon.assert.calledOnce(mockStream.pipe);
//We can get the stream that we piped to.
let writable = mockStream.pipe.getCall(0).args[0];
assert.equal(writable.path, './output.json');
//Tell mocha that the test is finished. Pass an error if any.
callback(err);
});
});
});
Later you could make separate functions like: createMockedStream. Or even extract all these preparations into a separate method and keep only asserts in a test.
From Sinon documentation, this has been removed from v3.0.0:
var stub = sinon.stub(object, "method", func);`
Instead you should use:
stub(obj, 'meth').callsFake(fn)

Categories

Resources