How to test an API that calls another external API in node - node.js

I'm using mocha, chai, and chai-http to test my simple API that routes calls from Slack to Habitica, integrating these two services.
I'm trying to start by creating tests, but I'm facing this issue: when I call my API, the code returns before the external API call. This is the code of the test:
var chai = require("chai");
var chaiHttp = require("chai-http");
var server = require("../src/app/index");
var should = chai.should();
chai.use(chaiHttp);
describe("/GET list", () => {
it("it should return a list of user\'s tasks", (done) => {
chai.request(server)
.post("/habitica")
.type("urlencoded")
.send({text: "list"})
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a("object");
res.body.should.have.property("success").eql("true");
done();
});
});
});
This is the code that is been called by the test:
app.post("/habitica", server.urlencodedParser, function(req, res) {
if (typeof req.body !== "undefined" && req.body) {
switch(req.body.text) {
case "list":
request({
url: GET_TASKS,
headers: { "x-api-user": process.env.HABITICA_USERID, "x-api-key": process.env.HABITICA_APITOKEN }
}, function (apiError, apiResponse, apiBody) {
if (apiError) {
res.send(apiError);
} else {
res.send(apiBody);
}
});
break;
default:
res.send({
"success": "false",
"message": "Still working on tasks creation"
});
}
}
});
This code returns before the call to Habitica return any value. This is the result of "npm test":
/GET list
1) it should return a list of user's tasks
0 passing (2s)
1 failing
1) /GET list
it should return a list of user's tasks:
Uncaught AssertionError: expected {} to have property 'success'
at chai.request.post.type.send.end (test/app.js:17:34)
at Test.Request.callback (node_modules/superagent/lib/node/index.js:706:12)
at IncomingMessage.parser (node_modules/superagent/lib/node/index.js:906:18)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
I've already searched in a lot of forums and sites:
Some people say that I shouldn't test code I don't own: this makes a lot of sense, but what should I've been testing since it is just a simple integration service?
Some people say that I should mock the external api result: but I won't been testing anything since, again, it is just an integration.
How can I solve this?
Thanks in advance.

You should mock the calls to external API & test how your app should behave in case of failure or success after calling the external API.
You can test different scenarios as follows
describe("/GET list", () => {
// pass req.body.text = 'list'
describe("when task list is requested", () => {
describe("when task list fetched successfully", () => {
// in beforeEach mock call to external API and return task list
it('returns tasks list in response', () => {
})
}),
describe("when error occurs while fetching task list", () => {
// in beforeEach mock call to external API and return error
it('returns error in response', () => {
})
})
}),
// when req.body.text != 'list'
describe("when task list is not requested", () => {
it('returns error in response', () => {
})
})
})

Related

Running multiple mocha files and suites with same context

I'm trying to run several integration tests with some shared context. The context being shared is a single express application, and I'm trying to share it across suites / files because it takes a few seconds to spin up.
I got it to work by instantiating a "runner" mocha test suite, that would have test functions that would just require each test file as needed, and this was working well (a side effect is that the test requiring the child test file would finish as "success" before any of the tests inside the file would actually run, but this was a minor issue)
// test-runner.js:
describe('Integration tests', function () {
let app
let log
this.timeout(300000) // 5 mins
before(function (done) {
app = require('../app')
app.initialize()
.then(() => {
done()
})
.catch(err => {
log.error(err)
done(err)
})
})
it('Running api tests...', (done) => {
require('./integration/api.test')(app)
done()
})
// ./integration/api.test.js:
module.exports = (app) => {
let api = supertest.agent(app)
.set('Content-Type', 'application/json')
describe('Authorization', () => {
describe('Trying to access authorization sections', () => {
it('should be denied for /home', async () => {
await api.get(`${baseUrl}/home`)
.expect(STATUS_CODE.UNAUTHORIZED)
})
...
The Problem:
I want to signal the test runner that all of the tests in the imported suite have finished, so I can call shutdown logic in the test runner and end the test cleanly. In standard test functions, you can pass a done function to signal that the code in the test is complete, so I wrapped each of the child tests in a describe block to use the after hook to signal that the whole test module was done:
// test-runner.js:
describe('Integration tests', function () {
let app
let log
this.timeout(300000) // 5 mins
before(function (done) {
app = require('../app')
app.initialize()
.then(() => {
done()
})
.catch(err => {
log.error(err)
done(err)
})
})
it('Running api tests...', (done) => {
require('./integration/api.test')(app, done)
})
// ./integration/api.test.js:
module.exports = (app, done) => {
let api = supertest.agent(app)
.set('Content-Type', 'application/json')
describe('All api tests', () => {
let api
before(() => {
api = supertest.agent(app)
.set('Content-Type', 'application/json')
})
after(() => {
done() // should be calling the done function passed in by test runner
})
describe('Authorization', () => {
describe('Trying to access authorization sections', () => {
it('should be denied for /home', async () => {
await api.get(`${baseUrl}/home`)
.expect(STATUS_CODE.UNAUTHORIZED)
})
...
but when I do this, the test suites just don't run. The default timeout will just expire, and if I set a higher timeout, it just sits there (waiting for the longer timeout). If I hook into a debug session, then the test exits immediately, and the after hook (and before!) never get called.
I'm open to other ideas on how to do this as well, but I haven't found any good solutions that that allow sharing some context between tests, while having them broken into different files.

Stub not getting called inside .then()

I am working on unit testing an Express application. I am trying to mock up my external dependencies (Express, database, etc) and am close to a break through.
However, I am having issues with stubs not being called from within a .then() inside my business logic.
A couple of the methods I am attempting to test are the following:
/**
* Ping
* A simple endpoint to poke for testing.
* #arg {*} request - Incoming request
* #arg {*} response - Outgoing response
* #arg {*} next - Next route in series
*/
ping: (request, response, next) => {
let elapsed = Date.now() - request.start;
response.status(200).json({
request_started: new Date(request.start).toISOString(),
request_duration: `${elapsed} milliseconds`
});
},
/**
* Registration
* Allows for registration of a user name and password.
* #arg {*} request - Incoming request
* #arg {*} response - Outgoing response
* #arg {*} next - Next route in series
*/
register: (request, response, next) => {
let username = request.body.username;
let password = request.body.password;
this.model.registerUser(username, password).then(ret => {
if (ret) {
response.status(201).send("Created");
} else {
response.status(400).send("Error processing registration request. Please try again.");
}
}).catch(err => {
response.status(400).send("Error processing registration request. Please try again.");
});
}
The model in register returns a Promise that wraps a call to a database and replies based on the outcome. I have a mock of this setup as follows:
mockModel: {
registerUser: sinon.stub().callsFake(function(user, pass) {
if (typeof user !== 'undefined') {
return Promise.resolve(pass === 'pass');
}
}),
getUser: sinon.stub().callsFake(function(user, pass) {
if (typeof user !== 'undefined' && pass === 'pass') {
return Promise.resolve({id: 9999});
} else {
return Promise.resolve(false);
}
})
}
I also have the response object mocked so I can pass it in and determine if it is called correctly:
mockRes: () => {
return {
status: sinon.stub().returnsThis(),
send: sinon.stub(),
json: sinon.stub()
};
}
The problem arises when I get to the tests:
describe('Register() method', function() {
this.beforeEach(function() {
req = mockReq(0, {}, {username: 'user', password: 'pass'});
res = mockRes();
base.BaseRoutes(router, null, base.methods, mockModel);
});
it('Returns a 201 Created message upon success', function() {
base.methods.register(req, res);
chai.expect(res.status).to.have.been.calledWith(201);
chai.expect(res.send).to.have.been.calledWith('Created');
});
});
The test here fails with the following error:
1) Base Methods
Register() method
Returns a 201 Created message upon success:
AssertionError: expected stub to have been called with arguments 201
at Context.<anonymous> (test\spec.js:50:50)
Stepping through with a debugger shows that the method is getting called, yet I'm getting this failure.
Other tests in the same suite that leverage the same mocked request/response work correctly but they don't use Promise calls (example: ping() method)
I suspect that it has something to so with scoping, but I'm not sure where things are going wrong.
After running through this a few more times, I found that it was not a scope issue, but an asynchronous issue. The test case was completing before the Promise resolved/rejected.
The piece that I was missing to close the loop was that my Express route handlers needed to return the Promise that they created to handle the database call:
register: (request, response, next) => {
let username = request.body.username;
let password = request.body.password;
return this.model.registerUser(username, password).then((ret) => {
if (ret) {
response.status(201).send("Created");
} else {
response.status(400).send("Error processing registration request. Please try again.");
}
}, (err) => {
response.status(400).send("Error processing registration request. Please try again.");
});
},
And then the test, perform my assertions in the then() on the returned Promise:
it('Returns a 201 Created message upon success', function(done) {
base.methods.register(req, res).then(x => {
chai.expect(res.status).to.have.been.calledWith(201);
chai.expect(res.send).to.have.been.calledWith('Created');
}).then(done, done);
});
it('Returns a 400 message upon failure', function(done) {
req = mockReq(0, {}, {username: 'user', password: 'fail'});
base.methods.register(req, res).then(x => {
chai.expect(res.status).to.have.been.calledWith(400);
chai.expect(res.send).to.have.been.calledWith(sinon.match.string);
}).then(done, done);
While there were examples of passing the Promise to the test and handling it there and examples on testing Express route handlers in isolation, I couldn't find examples that combined the two and tested it. Hopefully this can be of service to someone else.

Async Request to API return undefined in Jest

I am quiet new to testing, and specifically to Jest.
I am following several tutorials in which they handle asynchronous code in the manner I am attempting. My code seems to work when I am making a custom Promise that resolves with dummy data. But when I try to use axios to fetch from an external API, Jest gets as a response undefined.
// functions2.js
const axios = require("axios")
const fetch = () => {
axios.get("https://jsonplaceholder.typicode.com/users")
.then(res => res.data)
.catch(err => err);
}
module.exports = fetch;
// functions2.test.js
describe("async operation", ()=>{
it("should be defined", ()=>{
expect(fetch).toBeDefined()
}); // Passed
it("should fetch", async () => {
expect.assertions(1);
const data = await fetch();
expect(data).toBeTruthy();
}) // Did not pass, data is undefined
it("should fetch, using promises", () => {
expect.assertions(1);
return fetch().then(data => {
expect(data).toBeTruthy();
}) // Did not pass, got 0 assertions
})
})
In one tutorial I encountered that this has something to do with Jest running through Node.JS, but I don't know how to handle it because I don't know node.js.
Also, I followed a tutorial by Traversy Media, cloned his Git repo (https://github.com/bradtraversy/jest_testing_basics) and had the same problem (though in the video it worked)
The problem is because you are not returning the promise from fetch.
Update your functions2.js to something like:
const fetch = async () => {
return axios
.get("https://jsonplaceholder.typicode.com/users")
.then(res => res.data)
.catch(err => err);
};

Issues writing response actions in firebase functions

I have a function in index.js and I'm trying to resolve an account id from a response on an API. The original response is the following:
{
"data": {
"user": null,
"account": {
"id": 865,
"email": "mitch#gmail.com",
"plan_identifier": "dnsimple-business",
"created_at": "2018-06-24T00:55:29Z",
"updated_at": "2018-06-24T00:56:49Z"
}
}
}
And my code is the following:
exports.dnsCheckAuthorization = functions.https.onRequest((req, res) => {
cors(req, res, () => {
dnsimple.identity.whoami().then((response) => {
return res.status(200).send(response.data.account.id);
}).catch(error => (
res.status(500).send(error)
))
});
});
Finally, the error I receive in PostMan is the following:
Error: could not handle the request
And the error in the Firebase log is the following:
Function execution took 519 ms, finished with status: 'response error'
I've tried literally everything I can think of to get just the ID returned by this function and just can't figure it out. What am I missing?
UPDATE
I got this to work with the following code. Not quite what I want though. I want to return just the account id.
exports.dnsCheckAuthorization = functions.https.onRequest((req, res) => {
cors(req, res, () => {
dnsimple.identity.whoami().then((response) => {
var accountID = response.data.account.id;
console.log('account id is', accountID);
return res.status(200).json({id: accountID});
}).catch(error => (
res.status(500).send(error)
))
});
});
res.send() is an express only function. So it may not work if you did not create your server using express. Instead, you could use try something like this -
res.status(200).end(response.data.account.id)

Why does my timeout is exceeded?

I'm developing an API with Node.js and Express and i'm using Mocha and Supertest to write unit tests. I have a BIG file of tests which test every route with almost random parameters to see if my error handling works well.
Everything was great until, for no reason, my requests are starting to timeout.
This is more or less my code :
var supertest = require("supertest");
var should = require("should");
var server = supertest.agent("http://localhost:3000");
function requestAuth(url, type, auth, params, callback) {
if (params == null) {
server[type](url)
.type('form')
.auth(auth.email, auth.password)
.expect("Content-type",/json/)
.expect(200)
.end(callback);
}
else {
server[type](url)
.send(params)
.auth(auth.email, auth.password)
.type('form')
.expect("Content-type",/json/)
.expect(200)
.end(callback);
}
}
describe('Testing route 1', function() {
describe('Testing param 1 error handling', function() {
it('should return error 1', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
it('should return error 2', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
// etc
});
describe('Testing param 2 error handling', function() {
it('should return error 3', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
it('should return error 4', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
// etc
});
//etc
});
describe('Testing route 2', function() {
//etc
});
Except that i have of LOT of tests.
At some point, let say that when i'm testing route 8, every tests starts to fail with the following message :
12) Route 8 Testing Param 1 error handling should return error 1:
Error: timeout of 2000ms exceeded
at null.<anonymous> (/usr/lib/nodejs/mocha/lib/runnable.js:139:19)
at Timer.listOnTimeout (timers.js:92:15)
I really don't get it. Everything worked well since then, done is called at the end of every request, should be good. Nothing happens on the server side even though routes are ok. This is really weird...
Also, if the route 8 tests are getting weird, and that i comment route 7 tests for example, than route 9 tests will starts to act wrong.
I think this is coming from supertest. Is it possible that it is overloaded ? How could i fix that ?
Thanks in advance for your responses.
Your tests themselves have a time limit to complete your actions. This means that if the resource isn't set up and available in those two seconds, or if the test completes after two seconds it will fail. Use this.timeout = [milliseconds] as the first line of the failing test to extend the timeout.
MochaJS Test Level Timeouts

Resources