I use Jest + SuperTest to test API. I would like to get the token and store it globally (using agent() method. It works fine when I have hardcoded token and use agent() to set it globally like this:
const supertest = require("supertest");
const baseUrl = "https://gorest.co.in/public/v1";
const request = supertest
.agent(baseUrl)
.set(
"Authorization",
">>>here comes hardcoded token value<<<"
);
describe("Posts endpoint", () => {
it.only("should be able to create a post", async () => {
const resp = await request.get("/users");
const user_id = resp.body.data[0].id;
const response = await request.post("/posts").send({
title: "foo",
body: "bar",
user_id: user_id,
});
expect(response.statusCode).toBe(201);
});
});
but I don't know how to get the token from auth endpoint and pass it there instead of this hardcoded one. Here is the function of getting token in beforeAll().
let token = "";
beforeAll(async () => {
const response = await request(baseUrl).post("/auth").send({
username: "test#example.com",
password: "password",
});
token = response.body.access_token;
});
Does anyone have any idea how to handle that with SuperTest?
I think the order of your code is just slightly wrong. Although some functions get hoisted when testing, it might be clearer to write the code the way it should execute.
You have the write idea, as the beforeAll() is executed before a beforeEach or other test. This problem is that you are creating your request in Supertest before you run the beforeAll and get the token.
const supertest = require("supertest");
const baseUrl = "https://gorest.co.in/public/v1";
describe("Posts endpoint", () => {
let token = "";
beforeAll(async () => {
const response = await request(baseUrl).post("/auth").send({
username: "test#example.com",
password: "password",
});
token = response.body.access_token;
});
let request;
beforeEach(async () => {
request = supertest
.agent(baseUrl)
.set('Authorization', token)
;
});
it.only("should be able to create a post", async () => {
const resp = await request.get("/users");
const user_id = resp.body.data[0].id;
const response = await request.post("/posts").send({
title: "foo",
body: "bar",
user_id: user_id,
});
expect(response.statusCode).toBe(201);
});
});
Related
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.
I have a documents router which has router.post('/mine', [auth, uploadFile], async (req, res) => { ... }) route handler. The actual implementation of this route handler is below.
documents.js router
const createError = require('./../helpers/createError');
const auth = require('./../middlewares/auth');
const uploadFile = require('./../middlewares/uploadFile');
const express = require('express');
const router = express.Router();
router.post('/mine', [auth, uploadFile], async (req, res) => {
try {
let user = await User.findById(req.user._id);
let leftDiskSpace = await user.leftDiskSpace();
if(leftDiskSpace < 0) {
await accessAndRemoveFile(req.file.path);
res.status(403).send(createError('Your plan\'s disk space is exceeded.', 403));
} else {
let document = new Document({
filename: req.file.filename,
path: `/uploads/${req.user.username}/${req.file.filename}`,
size: req.file.size
});
document = await document.save();
user.documents.push(document._id);
user = await user.save();
res.send(document);
}
} catch(ex) {
res.status(500).send(createError(ex.message, 500));
}
});
module.exports = router;
I'm currently writing integration tests using Jest and Supertest. My current documents.test.js test file is below:
documents.test.js test file
const request = require('supertest');
const { Document } = require('../../../models/document');
const { User } = require('../../../models/user');
const fs = require('fs');
const path = require('path');
let server;
describe('/api/documents', () => {
beforeEach(() => { server = require('../../../bin/www'); });
afterEach(async () => {
let pathToTestFolder = path.join(process.cwd(), config.get('diskStorage.destination'), 'user');
// Remove test uploads folder for next tests
await fs.promises.access(pathToTestFolder)
.then(() => fs.promises.rm(pathToTestFolder, { recursive: true }))
.catch((err) => { return; });
// Remove all users and documents written in test database
await User.deleteMany({});
await Document.deleteMany({});
server.close();
});
describe('POST /mine', () => {
it('should call user.leftDiskSpace method once', async () => {
let user = new User({
username: 'user',
password: '1234'
});
user = await user.save();
let token = user.generateAuthToken();
let file = path.join(process.cwd(), 'tests', 'integration', 'files', 'test.json');
let documentsRouter = require('../../../routes/documents');
let errorToThrow = new Error('An error occured...');
user.leftDiskSpace = jest.fn().mockRejectedValue(errorToThrow);
let mockReq = { user: user };
let mockRes = {};
documentsRouter.post = jest.fn();
documentsRouter.post.mockImplementation((path, callback) => {
if(path === '/mine') {
console.warn('called');
callback(mockReq, mockRes);
}
});
const res = await request(server)
.post('/api/documents/mine')
.set('x-auth-token', token)
.attach('document', file);
expect(documentsRouter.post).toHaveBeenCalled();
expect(user.leftDiskSpace).toHaveBeenCalled();
});
});
});
I create mock post router handler for documents.js router. As you can see from mockImplementation for this route handler, it checks if the path is equal to '/mine' (which is my supertest endpoint), then calls console.warn('called'); and callback. When I run this test file, I can not see any yellow warning message with body 'called'. And also when POST request endpoint /api/documents/mine the server doesn't trigger my mock function documentsRouter.post. It has never been called. So I think the server's documents router is not getting replaced with my mock post route handler. It still uses original post route handler to respond my POST request. What should I do to test if my mock documentsRouter.post function have been called?
Note that my User model has a custom method for checking left disk space of user. I also tried to mock that mongoose custom method but It also doesn't work.
I'm learning tests and using :
Express
Jest
SuperTest
Sequelize
This is user.test.js, Everytime I ran the test, it create a user object in database, in app.get("/"),I have a:
User.findAll({});
so I would like to test someting more "generic" like a User Object, its possible, what do you suggest me?
const app = require("../app");
const request = require("supertest");
const { User } = require("../models/");
const db = require("../models");
describe('User Model', () => {
beforeAll(async () => {
const user = await db.User.create({ name: "Paulo", email: "blablblalbalbal#gmail.com", password: "123mudar" });
return user
})
it('List User', async () => {
const res = await request(app).get("/");
expect(res.body).toBe(user);
expect(res.statusCode).toBe(200);
});
});
You should mock your calls to DB or anywhere outside of your testing area.
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.."})
}
});
I'm making a test that includes an axios call to an endpoints. I want to mock out the call to the endpoint and return some custom data so that I'm not hitting a server everytime I'm testing.
Here is the code for the action that is in it's own action.js file.
login ({commit}, user) {
return new Promise((resolve, reject) => {
axios.post('https://backendauth.free.beeceptor.com/api/login', user)
.then(resp => {
console.log('response here')
console.log(resp)
console.log(resp.data)
const token = resp.data.success.token
const user = resp.data.user
localStorage.setItem('token', token)
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
commit('LOGIN_SUCCESS', token, user)
When logging in the action calls out to an endpoint and that endpoint returns a token which is stored in local storage. The token is also appended to the axios default headers so I don't have to attach the token everytime I want to make an axios call in the application.
Now to the test. I've written a test that sucessfully mocks the axios post however fails with an error when setting the default headers for the mock.
Here is the error
TypeError: Cannot read property 'headers' of undefined
37 | const user = resp.data.user
38 | localStorage.setItem('token', token)
> 39 | axios.defaults.headers.common["Authorization"] = "Bearer " + token;
| ^
40 | commit('LOGIN_SUCCESS', token, user)
41 | resolve(resp)
42 | })
Here is the test including the mock
import actions from "../../src/store/actions";
let url = "";
let body = "";
jest.mock("axios", () => ({
post: (_url, _body, config) => {
return new Promise((resolve) => {
resolve({'data' : {'success': {'token' : 'test'}}})
})
},
}))
describe('login action', () => {
it("let the user login and access login success mutator", async() => {
const commit = jest.fn()
const username = 'test'
const password = 'test'
await actions.login({commit}, {username, password})
})
})
The jest mock is a full mock which returns a promise when called, with the data I need, however the code fails when trying to set the headers. Is there a way I can mock this out as well or do I need to write the mock in a different way?
Here is how you do it for anyone with the same problem. Npm install axios-mock-adapter and then use the following code but change it for your needs.
import actions from "../../src/store/actions";
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
describe('login action', () => {
it("let the user login and access login success mutator", async() => {
let mockAdapter = new MockAdapter(axios);
mockAdapter.onPost('https://hotel-dev.devtropolis.co.uk/api/apilogin').reply(200, {
token: 'test token',
user: {username: 'test', password: 'test'}
});
const commit = jest.fn()
const username = 'test'
const password = 'test'
await actions.login({commit}, {username, password})
expect(axios.defaults.headers.common.Authorization).toBe('Bearer test token')
expect(commit).toHaveBeenCalledWith(
"LOGIN_SUCCESS",'test token', {username, password})
})
})