I'm starting to test my application using Jest and Supertest (for endpoints). Tests work smoothly but Jest detects 2 open handles after running tests which prevents Jest from exiting cleanly.
This open handles are generated by an external async function that is being called within my test file. I'm using an external function to request a JWT Token from Auth0 API; but that request to Auth0 also provides in it's response crucial information to pass the endpoint's middlewares (more info about this below). Two things to have in mind here:
So far, I can't avoid requesting a token from Auth0 because that response, as I said, also includes a user object with key information. Auth0 sets this object outside of the body response, at that same level, but not within it. That information is key to pass the endpoint's middleware.
I've isolated all the errors to be sure that the problem shows up only when I call the external async function that requests from Auth0 API's the token and user info; the issue is generated by using that function (called getToken) within the test file.
Test file code
import app from "../app";
import mongoose from "mongoose";
import supertest from "supertest";
import { getToken } from "../helpers";
import dotenv from "dotenv";
import * as config from "../config";
dotenv.config();
const api = supertest(app);
let authToken: any;
let db: any;
beforeAll(async() => {
try {
mongoose.connect(config.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
db = mongoose.connection;
db.on("error", console.error.bind(console, "Console Error:"));
db.once("open", () =>
console.log(`App connected to "${db.name}" database`)
);
authToken = await getToken()
} catch (err) {
return err
}
});
describe("GET /interview/:idCandidate", () => {
test("With auth0 and read permissions", async () => {
await api
.get("/interview/1")
.set("Authorization", "Bearer " + authToken)
.expect(200)
});
});
afterAll(async () => {
try {
await db.close();
} catch (err) {
return err;
}
});
getToken external function that requests info to Auth0 API
The getToken function that is imported from external module is as follows:
import axios from 'axios'
var options = {
url: //url goes here,
form:
{
// form object goes here
},
json: true
};
const getToken = async () => {
try {
const tokenRequest = await axios.post(options.url, options.form)
return tokenRequest.data.access_token
} catch (err){
return err
}
}
export default getToken;
Issue
Once my tests are run, they run as expected until Jest's --detectOpenHandles configuration detects the two following open handles:
Jest has detected the following 2 open handles potentially keeping Jest from exiting:
● TLSWRAP
60 | case 0:
61 | _a.trys.push([0, 2, , 3]);
> 62 | return [4 /*yield*/, axios_1.default.post(options.url, options.form)
| ^
63 | ];
64 | case 1:
at RedirectableRequest.Object.<anonymous>.RedirectableRequest._performRequest (node_modules/follow-redirects/index.js:265:24)
at new RedirectableRequest (node_modules/follow-redirects/index.js:61:8)
at Object.request (node_modules/follow-redirects/index.js:456:14)
at dispatchHttpRequest (node_modules/axios/lib/adapters/http.js:202:25)
at httpAdapter (node_modules/axios/lib/adapters/http.js:46:10)
at dispatchRequest (node_modules/axios/lib/core/dispatchRequest.js:53:10)
at Axios.request (node_modules/axios/lib/core/Axios.js:108:15)
at Axios.<computed> [as post] (node_modules/axios/lib/core/Axios.js:140:17)
at Function.post (node_modules/axios/lib/helpers/bind.js:9:15)
at call (dist/helpers/getToken.js:62:54)
at step (dist/helpers/getToken.js:33:23)
at Object.next (dist/helpers/getToken.js:14:53)
at dist/helpers/getToken.js:8:71
at __awaiter (dist/helpers/getToken.js:4:12)
at Object.token (dist/helpers/getToken.js:56:34)
at call (dist/test/api.test.js:87:48)
at step (dist/test/api.test.js:52:23)
at Object.next (dist/test/api.test.js:33:53)
at dist/test/api.test.js:27:71
at __awaiter (dist/test/api.test.js:23:12)
at dist/test/api.test.js:72:32
● TLSWRAP
141 | switch (_a.label) {
142 | case 0: return [4 /*yield*/, api
> 143 | .get("/interview/1")
| ^
144 | .set("Authorization", "Bearer " + authToken)
145 | .expect(200)];
146 | case 1:
at Test.Object.<anonymous>.Test.serverAddress (node_modules/supertest/lib/test.js:61:33)
at new Test (node_modules/supertest/lib/test.js:38:12)
at Object.get (node_modules/supertest/index.js:27:14)
at call (dist/test/api.test.js:143:26)
at step (dist/test/api.test.js:52:23)
at Object.next (dist/test/api.test.js:33:53)
at dist/test/api.test.js:27:71
at __awaiter (dist/test/api.test.js:23:12)
at Object.<anonymous> (dist/test/api.test.js:139:70)
I'm certain that the error is coming from this getToken async function.
Why am I not mocking the function?
You might be wondering why am I not mocking that function and as I said before, when Auth0 responds with the token (which refreshes quite often by the way), it also responds with info regarding the user, and that info goes outside the response.body. As a matter of fact, it goes at the same hierarchical level as the body. So, if I you wanted to mock this function, I would have to set the Authorization header with the bearer token on one side (which is easy to do with Supertest), and the user info provided by Auth0 on the other side; but this last step is not possible (at least as far as I know; otherwise, how do you set a user info property at the same hierarchy level as the body and not within it?)
Things I've tried
I've tried adding a longer timeout to the test and to beforeAll(); I've tried adding the done callback instead of using async/await within beforeAll() and some other not very important things and none of them solves the open handle issue. As a matter of fact, I've checked if the request process to Auth0 API is closed after the response and effectively, that connection closes but I still get open handle error after running the tests.
Any idea would be highly appreciated!
I've been also struggling with a similar problem today and failed to find a definite solution, but found a workaround. The workaround (posted by alfreema) is to put the following line before you make a call to axios.post:
await process.nextTick(() => {});
This seems to allow Axios to complete its housekeeping and be ready to track new connections opened afterwards. This is just my speculation, I hope someone else can shed more light on it and provide a proper solution.
Thanks #RocketR, it's really work.
await process.nextTick(() => { });
const newData = await axios.post(`${url}`, pattern);
const token = await axios.post(`${url}/token`, tokenData,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
Every call I will call await process.nextTick(() => {});.
Example:
await process.nextTick(() => {});
await supertest(app)
.get("/api/getBooking")
.set({ Authorization: `Bearer ${TOKEN}` })
.then((response) => {
expect(response.statusCode).toBe(200);
})
Related
I am currently working on a web app to manage an external database. I am not very familiar with express or NodeJS at this point so I wanted to ask how to send a JSON object to the client sides console without getting undefined?
I have this function to connect then select the what I need and afterwards I converted my JSON object to an array of JSON objects. It displays the data fine in the console as well.
async function connect() {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
} catch (err) {
console.error('Unable to connect to the database:', error);
}
info = await sequelize.query('select * from LeadsInformation', { type: QueryTypes.SELECT });
const details = JSON.stringify(info);
console.log(details);
detailsArray = JSON.parse(details);
console.log(detailsArray);
}
Everything works fine in here, I can get the data and display it in the terminal.
This is my GET route:
app.get("/list", (req, res) => {
connect();
res.json(detailsArray)
});
I have tried a couple of suggested ways based on other explanations and code snippets but none of them has worked so far so I left it like that. I thought foreaching through the data itself in the request would be a solution but it did not work. I also tried using the JSON itself and trying to display it and also tried using the body parser library. Though the library has not been updated for two years. Also I am using axios to fetch the data. It works fine when I try sending a simple string like "hello world" for example.
Is there anything that I'm missing or do you have any other solutions? I would also appreciate an explanation as well if possible.
Edit: It might also have to do something with how I am getting the response in the frontend. I'll look into that as well and will update this thread if I sort it out!
This is the way I get the response. I am currently trying to show in the console. I am using axios API.
Axios({
method: "GET",
url: "http://localhost:5000/list",
headers: {
"Content-Type": "application/json"
}
}).then(res => {
console.log(res.data.json);
});
Probably you have undefined in route because connect function doesn't return anything.
Also connect is an async function it means that it returns Promise and you have to call .then method or use await to get value from it.
Here is the code snippet with fixes that I described above.
async function connect() {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
} catch (err) {
console.error('Unable to connect to the database:', error);
}
info = await sequelize.query('select * from LeadsInformation', { type: QueryTypes.SELECT });
const details = JSON.stringify(info);
detailsArray = JSON.parse(details);
return detailsArray;
}
app.get("/list", async (req, res) => {
const result = await connect();
res.json(result)
});
Notice that in the router handler function I also use async and await because I call connect which is an asynchronous function.
The solution above did work and also another problem I had was that I wasn't getting the response correctly.
I ended up getting the response to the frontend after changing my code to the following from:
console.log(res.data.json);
To:
console.log(res.data[1]);
I'm attempt to connect to firebase/firestone using the nodejs SDK,however I it doesn't connect. I've attempted to connect multiple times, using setInterval but nothing works.
First, I initialize the the firebase using the credentials and the databaseURL, after this I get the databaseRef, and in the end I attempt to write to the database.
I've checked the ./info/connected on setInterval with timeout of 1000ms and mocha --timeout flag to 5000ms, and always marks as offline.
I've checked the credentials, when is a wrong credential or config json, they give an JSON parse error message(cause I have several storage instances, each connected according to a flag spawned during the execution time).
I'm using the TDD approach on my application, so, I have to mock the entire database and check against the resulted values of each operation. I've wrote a controller for the task of handling the firebase/firestone work, but I if I can't connected it has no use.
The code goes here:
const analyticsFirebaseMock = admin.initializeApp({
credentials: admin.credential.cert(analyticsCredentials),
databaseURL: process.env.ANALYTICS_FIREBASE_URL
}, 'analyticsMock')
const analyticsDbRef = analyticsFirebaseMock.database()
beforeEach(() => {})
afterEach(() => sinon.restore())
describe('POST - /analytics', () => {
it('should save the analytics data for new year', async (done) => {
const itens = 1
const price = 599.00
setInterval(() => {
clockAnalyticsDbRef.ref(`.info/connected`).once('value', (value) => {
if (value.val() === true) console.log('connected')
else console.error('offline')
})
}, 1000)
await analytics.updateAnalytics(user, itens, price)
await analyticsDbRef.ref(`${user}`).once('value', (value) => {
expect(R.view(userLens, value)).to.be.equals(user)
done()
})
})
})
In the above code, I use async/await on analyticsDbRef cause of the asynchronous characteristic of the js. Call the controller, await the query result, conclude with done. The test fails with timeout, expecting done to be called.
What could I doing wrong?
I'm trying to do some integration tests for my api in express.
My API's structure is something like:
app -> routes -> controllers -> services
Because I already have unit tests, my idea is only test that all that components are connected in the correct way.
So my idea was created an stub with Sinon for the service, and only check the responses of the controller with supertest.
When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.
I think it's because the app is already saved in cache as a module, so sinon can't stub the service.
Some examples of my code:
controller.js
const httpStatus = require('http-status');
const { service } = require('../services/croupier');
/**
* Execute lambda tasks for candidates
* #public
*/
exports.task = async (req, res, next) => {
try {
const result = await service({
body: req.body,
authorizer: req.authorizer
});
console.log('res', result);
res.status(httpStatus.OK).json(result);
} catch (error) {
next(error);
}
};
foo.integration.test.js
const request = require('supertest');
const httpStatus = require('http-status');
const sinon = require('sinon');
const mongoose = require('../../../database');
const deleteModule = module => delete require.cache[require.resolve(module)];
const requireUncached = module => {
deleteModule(module);
return require(module);
};
describe('Foo - Integration Test', async () => {
describe('POST /v1/foo', () => {
const fooService = require('../../services/foo');
const stub = sinon.stub(fooService, 'service');
let db;
before(async () => {
db = await mongoose.connect();
});
afterEach(async () => {
sinon.restore();
});
after(async () => {
await db.close();
});
it('the api should response successfully', async () => {
stub.returns({});
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.OK);
});
it('the api should response with an error', async () => {
stub.throwsException();
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.INTERNAL_SERVER_ERROR);
});
});
});
The other integration tests have the same structure. I've also tried using proxyquire but didn't work.
Also I tried deleting cache of de app.js with any success.
Any ideas?
Context: integration test.
I agree with your idea: "test that all that components are connected in the correct way". Then what you need is spy, not stub. When there is a case / condition, you need to setup preconfigured/dummy data (up mongodb with specific data), turn on HTTP server, call HTTP request with specific data (post / get with specific query), and check the HTTP response for correct status, etc. The spy needed to check/validate/verify whether your service get called with correct parameter and response with correct result. This test validate you have correctly configured route - controller to a service for specific HTTP request.
You must have question: How to test negative scenario? For example: 404, 500. Then you need to know which specific scenario do what, which result negative condition. For example: if request come with unknown ID query, then response will be 404. Or if express not connected to database, then response will be 500. You need to know the real scenario, and again provide the require setup to produce the negative response.
For problem: "When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.". There are several possible solutions, the main point is: you must make sure that the conditions for specific scenario/case are correctly prepared.
You can do:
create sandbox, to make sure no other stub service run between test cases.
start up fresh http (and or db) server before and shut down the server after the test run for each services, (for example: start the app and use real http client - as alternative to supertest)
run on debug mode to find out why the second stub not run or not get called or not work,
change implementation from stub to spy, you have already had a unit test, you just need to check whether the service get called or not, and then check the overall response.
Hope this helps.
I am writing a grapqhl server that has a simple logout mutation. Everything works as expected when I run the server and I can log out by destroying the session and clearing the cookie just fine.
Here is the resolver:
export default async (root, args, context) => {
console.log("THIS WILL LOG")
await new Promise((res, rej) =>
context.req.session.destroy(err => {
if (err) {
return rej(false);
}
context.res.clearCookie("qid");
return res(true);
})
);
console.log("NEVER HERE BEFORE TIMEOUT");
// 4. Return the message
return {
code: "OK",
message: "You have been logged out.",
success: true,
item: null
};
};
I am attempting to write a simple test just to verify that the req.session.destroy and res.clearCookie functions are actually called. At this point I AM NOT attempting to test if a cookie is actually cleared, as I am not actually starting up the server, I am just testing that the graphql resolver was ran correctly and that it called the right functions.
Here is a portion of my test:
describe("confirmLoginResolver", () => {
test("throws error if logged in", async () => {
const user = await createTestUser();
const context = makeTestContext(user.id);
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
context.res.clearCookie = jest.fn();
// this function is just a helper to process my graphql request.
// it does not actually start up the express server
const res = await graphqlTestCall(
LOGOUT_MUTATION, // the graphql mutation stored in a var
null, // no variables needed for mutation
null // a way for me to pass in a userID to mock auth state,
context // Context override, will use above context
);
console.log(res);
expect(context.req.session.destroy).toHaveBeenCalled();
// expect(res.errors.length).toBe(1);
// expect(res.errors).toMatchSnapshot();
});
});
Again, everything works correctly when actually running the server. The problem is that when I attempt to run the above test, I always get a jest timeout:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
The reason is that the await section of above resolver will hang because it's promise.resolve() is never being executed. So my console will show "THIS WILL LOG", but will never get to "NEVER HERE BEFORE TIMEOUT".
I suspect I need to write a better jest mock to more accurately simulate the callback inside of context.req.session.destroy, but I can not figure it out.
Any ideas how I can write a better mock implementation here?
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
Is not cutting it. Thoughts?
Try
context.req.session.destroy = jest
.fn()
.mockImplementation((fn) => fn(false));
I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.
Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});
So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.