How to test express controller fetching a file with Jest - express

I had a simple endpoint using Express to allow user to download a csv file.
How should I make a test with just Jest for a file download endpoint
I'm not sure which function or mock should I use to test out this scenario, as it returns with Number of calls: 0 for below test
controller.js
const getFile = async (req, res, next) => {
try {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename=sample.csv');
const csv = 'ID\n1\n2\n3';
res.send(csv);
} catch (e) {
next(
new HttpException(
'internal error',
'file download error',
e.message,
),
);
}
}
controller.test.js
test('should successfully download the csv', async () => {
const mockReq = {};
const mockRes = {
send: jest.fn(),
};
await controller.getFile(mockReq, mockRes, jest.fn());
expect(mockRes.send).toHaveBeenCalledWith({ 'Content-Type': 'text/csv' });
});

In case anyone face similar problem like me, I think the easiest way is using supertest library. This library support HTTP assertions, so I can test at route level:
const request = require('supertest');
...
const response = await request(app).get(
'/api/.../download-file',
);
expect(response.status).toEqual(200);
expect(response.headers['content-type']).toMatch('text/csv; charset=utf-8');
expect(response.headers['content-disposition']).toMatch(
'attachment; filename=' + 'sample.csv',
);

Related

How to test `verify` of an express middleware in Jest

I have a function which returns a middleware as such:
const jsonParser = () => {
return express.json({
limit: '5mb',
verify: (req, res, buf) => {
// If the incoming request is a stripe event,
if (req.headers['some-header']) {
httpContext.set('raw-body', buf.toString());
}
},
});
};
I would like to test that the httpContext.setis indeed called when the some-header header is present.
My test:
describe('jsonParser middleware', () => {
it('sets the http context', async () => {
const req = {
headers: {
'some-header': 'some-sig',
'content-type': 'application/json',
},
body: JSON.stringify({
some: 'thing',
}),
};
const res = {};
const middleware = jsonParser();
middleware(req, res, () => {});
expect(httpContext.set).toHaveBeenCalled();
});
});
I have no idea how to make the test run the function passed to verify. Express docs state that the content type should be json, but nothing more. Anyone that can point me in the right direction is highly appreciated.
Thank you.
as mentioned in the comments i want to give you an example of an integration test which tests the header and jsonwebtoken. i am also using the express framework but i wrote my code in JS.
this is a test for creating a forumpost in a forum i built. a middleware is checking for the token of the user so this case could be similiar to yours.
const request = require('supertest');
test('create authorized 201', async () => {
const forumCountBefore = await ForumPost.countDocuments();
const response = await request(app)
.post('/api/forumPosts')
.set({
Authorization: `Bearer ${forumUserOne.tokens[0].token}`,
userData: {
userId: forumUserOneId,
email: 'forum#controller.com',
username: 'forum',
},
})
.send(forumPost)
.expect(201);
expect(response.body.message).toBe('created forumPost');
const forumCountAfter = await ForumPost.countDocuments();
expect(forumCountBefore + 1).toBe(forumCountAfter);
});
i am using mongoDB thats why i use ForumPost.countDocuments to count the amount of entries in the DB.
as you can see in the test i use supertest (imported as request) to send an http call. in the set block i set the authorization token. this causes the middleware to be executed in the integration test.
the test can only pass when the code of the middleware gets executed correctly so it should cover the code of your middleware.

Calling an api and return these values in Nodejs

I am trying to build a small website. In that i using React for frontend, Nodejs for backend, and some third party api. Here my idea is, first to post the form data to nodejs. And from then i accepting that data in node and need to call an external api. For this purpose i am using axios. After receiving values from my api i have to send that value back to react application. And when i run my code in postman, the output is {}. I think that i am not getting values from my api but dont know how to resolve this. And i am new to these technologies. Someone pls help me to sort out this problem. Thanking you in advance. Here is my what i have tried so far.
const express = require('express');
const axios = require('axios');
const router = express.Router();
const request = require('request');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended : false}));
router.get('/', (req, res) => {
res.send(" Express Homepage is running...");
});
async function callApi(emailid, pswd) {
return axios({
method:'post',
url:'http://51.X.X/api/login',
data: {
"email": `${emailid}`,
"password": `${pswd}`
},
headers: {'Content-Type': 'application/json' }
})};
callApi().then(function(response){
return response.data;
})
.catch(function(error){
console.log(error);
})
app.post('/api/login', (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
const data = callApi(emailid, pswd);
if(data) {
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
use async/await syntax to handle asynchronous calls
app.post('/api/login', async (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
const data = await callApi(emailid, pswd);
if(data) {
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
The problem is you are not waiting for async call to finish.
use async-await as mentioned in official doc https://www.npmjs.com/package/axios
function callAPI(){
const response = await axios({
method:'post',
url:'http://51.X.X/api/login',
data: {
"email": `${emailid}`,
"password": `${pswd}`
},
headers: {'Content-Type': 'application/json' }
})};
return response
}
app.post('/api/login', async (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
//add try catch to catch exception
const data = await callApi(emailid, pswd);
if(data) {
//check for response from axios in official doc and send what data you
want to send
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});

Jest / Supertest Error - Right-hand side of 'instanceof' is not callable

When using supertest like so,
import app from "../../src/app";
import request from "supertest";
describe("GET / - a simple api endpoint", () => {
it("Hello API Request", () => {
const result = request(app)
.get("/api/location/5eda6d195dd81b21a056bedb")
.then((res) => {
console.log(res);
})
// expect(result.text).toEqual("hello");
// expect(result.status).toEqual(200);
});
});
Im getting "Right-hand side of 'instanceof' is not callable".
at Response.toError (node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (node_modules/superagent/lib/response-base.js:123:16)
at new Response (node_modules/superagent/lib/node/response.js:41:8)
at Test.Request._emitResponse (node_modules/superagent/lib/node/index.js:752:20)
at node_modules/superagent/lib/node/index.js:916:38
at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/parsers/json.js:19:7)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
status: 500,
text: `"Right-hand side of 'instanceof' is not callable"`,
method: 'GET',
path: '/api/location/5eda6d195dd81b21a056bedb'
This is just with supertest, the API works when using Postman.
Rest of the code for this call,
router.get(
"/location/:id",
(req, res) => {
locationController.getLocation(req, res);
}
);
const getLocation = async (req: Request, res: Response): Promise<void> => {
const { id } = req.params;
const location = await data.readRecord(id, Location);
res.status(location.code).json(location.data);
};
const readRecord = async (id: string, model: IModel): Promise<Response> => {
try {
const response = await model.findById(id);
if (response == null) return { code: 404, data: `ID ${id} Not Found` };
return { code: 200, data: response };
} catch (error) {
return errorHandler(error);
}
};
Is there a configuration im missing for supertest and typescript?
This approach worked,
import request = require("supertest");
import app from "../../src/app";
describe("GET/ api/location/id", () => {
it("should connect retrieve record and retrieve a code 200 and json response", async () => {
const res = await request(app)
.get(`/api/location/${id}`)
expect(res.status).toBe(200);
expect(res.body._id).toBe(`${id}`);
});
});
If you don't want to use "await" in your code , you can use "done()" in callback function.
like this.
import app from "../../src/app";
import request from "supertest";
describe("GET / - a simple api endpoint", () => {
it("Hello API Request", (done) => {
const result = request(app)
.get("/api/location/5eda6d195dd81b21a056bedb")
.then((res) => {
console.log(res);
expect(res.text).toEqual("hello");
expect(res.status).toEqual(200);
done();
//done() function means this test is done.
})
});
});
Awaiting the expect call (with Jest) worked for me.
await expect(...).rejects.toThrow()

How to send a request from Nuxt.js client over Nuxt.js server and receive the response back to the client

I'm developing a Vue.js application which has only frontend (no server) and send a lot of requests to different APIs. The originally quite simple app became more complex. And there are problems with some APIs, because browsers do not accept the responses due to CORS. That is why I'm trying to test, if I can migrate the app to Nuxt.js.
My approach is as follows (inspired by this comment), but I expect, that there is probably a better way to send the requests from the client over the server.
pages/test-page.vue
methods: {
async sendRequest(testData) {
const response = await axios.post('api', testData)
// Here can I use the response on the page.
}
}
nuxt.config.js
serverMiddleware: [
{ path: '/api', handler: '~/server-middleware/postRequestHandler.js' }
],
server-middleware/postRequestHandler.js
import axios from 'axios'
const configs = require('../store/config.js')
module.exports = function(req, res, next) {
let body = ''
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
if (req.hasOwnProperty('originalUrl') && req.originalUrl === '/api') {
const parsedBody = JSON.parse(body)
// Send the request from the server.
const response = await axios.post(
configs.state().testUrl,
body
)
req.body = response
}
next()
})
}
middleware/test.js (see: API: The Context)
export default function(context) {
// Universal keys
const { store } = context
// Server-side
if (process.server) {
const { req } = context
store.body = req.body
}
}
pages/api.vue
<template>
{{ body }}
</template>
<script>
export default {
middleware: 'test',
computed: {
body() {
return this.$store.body
}
}
}
</script>
When the user makes an action on the page "test", which will initiate the method "sendRequest()", then the request "axios.post('api', testData)" will result in a response, which contains the HTML code of the page "api". I can then extract the JSON "body" from the HTML.
I find the final step as suboptimal, but I have no idea, how can I send just the JSON and not the whole page. But I suppose, that there must be a much better way to get the data to the client.
There are two possible solutions:
Proxy (see: https://nuxtjs.org/faq/http-proxy)
API (see: https://medium.com/#johnryancottam/running-nuxt-in-parallel-with-express-ffbd1feef83c)
Ad 1. Proxy
The configuration of the proxy can look like this:
nuxt.config.js
module.exports = {
...
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
proxy: {
'/proxy/packagist-search/': {
target: 'https://packagist.org',
pathRewrite: {
'^/proxy/packagist-search/': '/search.json?q='
},
changeOrigin: true
}
},
...
}
The request over proxy can look like this:
axios
.get('/proxy/packagist-search/' + this.search.phpLibrary.searchPhrase)
.then((response) => {
console.log(
'Could get the values packagist.org',
response.data
)
}
})
.catch((e) => {
console.log(
'Could not get the values from packagist.org',
e
)
})
Ad 2. API
Select Express as the project’s server-side framework, when creating the new Nuxt.js app.
server/index.js
...
app.post('/api/confluence', confluence.send)
app.use(nuxt.render)
...
server/confluence.js (simplified)
const axios = require('axios')
const config = require('../nuxt.config.js')
exports.send = function(req, res) {
let body = ''
let page = {}
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
const parsedBody = JSON.parse(body)
try {
page = await axios.get(
config.api.confluence.url.api + ...,
config.api.confluence.auth
)
} catch (e) {
console.log('ERROR: ', e)
}
}
res.json({
page
})
}
The request over API can look like this:
this.$axios
.post('api/confluence', postData)
.then((response) => {
console.log('Wiki response: ', response.data)
})
.catch((e) => {
console.log('Could not update the wiki page. ', e)
})
Now with nuxtjs3 :
nuxtjs3 rc release
you have fetch or useFetch no need to import axios or other libs, what is great, automatic parsing of body, automatic detection of head
fetching data
you have middleware and server api on same application, you can add headers on queries, hide for example token etc
server layer
a quick example here in vue file i call server api :
const { status } = await $fetch.raw( '/api/newsletter', { method: "POST", body: this.form.email } )
.then( (response) => ({
status: response.status,
}) )
.catch( (error) => ({
status: error?.response?.status || 500,
}) );
it will call a method on my server, to init the server on root directory i created a folder name server then api, and a file name newsletter.ts (i use typescript)
then in this file :
export default defineEventHandler(async (event) => {
const {REST_API, MAILINGLIST_UNID, MAILINGLIST_TOKEN} = useRuntimeConfig();
const subscriber = await readBody(event);
console.log("url used for rest call" + REST_API);
console.log("token" + MAILINGLIST_TOKEN);
console.log("mailing list unid" + MAILINGLIST_UNID);
let recipientWebDTO = {
email: subscriber,
subscriptions: [{
"mailingListUnid": MAILINGLIST_UNID
}]
};
const {status} = await $fetch.raw(REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
Authorization: MAILINGLIST_TOKEN,
},
}).then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
event.res.statusCode = status;
return "";
})
What are the benefits ?
REST_API,MAILING_LIST_UNID, MAILING_LIST_TOKEN are not exposed on
client and even file newsletter.ts is not available on debug browser.
You can add log only on server side You event not expose api url to avoid some attacks
You don't have to create a new backend just to hide some criticals token or datas
then it is up to you to choose middleware route or server api. You don't have to import new libs, h3 is embedded via nitro with nuxtjs3 and fetch with vuejs3
for proxy you have also sendProxy offered by h3 : sendProxy H3
When you build in dev server and client build in same time(and nothing to implement or configure in config file), and with build to o, just don deploy your project in static way (but i think you can deploy front in static and server in node i don't know)

Jest promise not resolving

I am testing my express server in Jest. All test pass but Jest doesn't close or exit. I have no idea why but I believe my server is closing after the test runs. I tried running yarn jest --detectOpenHandles and the response came back saying "Jest has detected the following 1 open handle potentially keeping Jest from exiting:
Promise"
Here is my code...
const request = require('supertest');
const app = require('../server');
const server = app.listen(8000, '127.0.0.1');
afterAll(() => {
server.close();
});
describe('GET /', () => {
it('should respond with JSON (list of all users)', async () => {
try {
const response = await request(server).get('/')
.expect('Content-Type', /json/);
expect(response.statusCode).toBe(200);
} catch (e) {
console.log(e);
}
});
});
I can't seem to understand why the promise isn't resolving if that's actually the issue. My express app is pulling data from Firebase and returning the data once a get request is made. Any ideas on what's going on?
In Server.js file I connect to Firebase maybe this is the issue?
const db = admin.database();
const ref = db.ref('users');
app.get('/', (req, res) => {
// Attach an asynchronous callback to read the data at our posts reference
ref.on('value', function(snapshot) {
res.send(parseUsers(snapshot.val()));
res.status(200);
}, function (errorObject) {
res.send([],'The read failed: ' + errorObject.code);
res.status(500);
});
} );