Related
I am trying to create a backend application using Node.js and Express to get post and modify user data from a .json file. Here is my app.js file:
const express = require("express");
const fs = require("fs");
//setting up the express router
const app = express();
app.use(express.json());
//write the code for routes here
app.post("/add", function(req, resp){
var jsonObject = req.body;
var jsonFile = fs.readFileSync("get.json", "UTF8");
var jsonArray = JSON.parse(jsonFile);
jsonArray.push(jsonObject);
jsonFile = JSON.stringify(jsonArray);
resp.json(jsonFile);
fs.writeFileSync("get.json",jsonFile,"utf-8");
});
app.get('/view/:id?', function(req, resp){
var queryURL = url.parse(req.url, true).query;
var jsonFile = fs.readFileSync("get.json", "UTF8");
var data = JSON.parse(jsonFile);
var id = req.params.id;
// if(typeof queryURL.id === "undefined" || queryURL.id == 0){
// resp.json(data);
// }else{
// resp.json(data[queryURL.id-1]);
// }
if (id >= 0) {
data.array.every(user => {
if (user.id === id) {
resp.json(user);
return false;
} else {
console.log("Next")
}
});
} else{
resp.json(data);
}
});
app.patch("/edit/:id", function(req, res){
let userID = req.params.id;
let userFile = fs.readFileSync("get.json", "UTF-8");
let userArray = JSON.parse(userFile);
let reqUserObject = req.body;
let newUserArray = userArray.map(user => {
if (user.id === userID) {
updatedUser = {...user, ...reqUserObject};
return updatedUser;
} else {
return user;
}
});
userFileData = JSON.stringify(newUserArray);
res.json(userFileData);
fs.writeFileSync("get.json", userFileData, "UTF-8");
});
module.exports = app;
All methods work except for
app.get('/view/:id?', function(req, resp)
This Method has an optional query parameter id, if you pass an id you get a user with an id equal to that id. If you dont pass in an id, you get all the users from get.json.
The json file (get.json) I used is:
[{"id":"1","name":"updated name","age":"22","gender":"Male","email":"userone#gmail.com"},{"id":"2","name":"user two","age":"24","gender":"Female","email":"usertwo#gmail.com"},{"id":"3","name":"user three","age":"23","gender":"Male","email":"userthree#gmail.com"},{"id":"4","name":"user four","age":"21","gender":"Male","email":"userfour#gmail.com"}]
And my test file is:
const request = require("supertest");
const app = require("../app");
const md5 = require("md5");
const fs = require("fs");
//updating a user profile
test("Updating a user", async () => {
await request(app)
.patch("/edit/1")
.send({
name: "updated name",
})
.expect(200);
setTimeout(() => {
const data = JSON.parse(fs.readFileSync("../post.json"));
expect(data.length).toBe(5);
expect(data[0].name).toBe("updated name");
}, 1000);
});
// // posting a data
test("Posting a new data", async () => {
await request(app)
.post("/add")
.send({
id: "5",
name: "user new",
age: "36",
gender: "Female",
email: "usernew#gmail.com",
})
.expect(200);
setTimeout(() => {
const data = JSON.parse(fs.readFileSync("../post.json"));
expect(data.length).toBe(5);
}, 1000);
});
//checking the get route
test("Getting all the user data", async () => {
const response = await request(app).get("/view").expect(200);
expect(response.body.length).toBe(4);
expect(md5(response.body)).toBe("f1d3ff8443297732862df21dc4e57262");
});
//getting profile of a user based on id
test("Getting a single user data", async () => {
const response = await request(app).get("/view?id=2").expect(200);
expect(response.body.length).toBe(1);
expect(md5(response.body)).toBe("93b885adfe0da089cdf634904fd59f71");
});
When I run the tests I get:
rm -rf ./test-report.xml && CI=true ./node_modules/.bin/jest
--testResultsProcessor ./node_modules/jest-junit-reporter --forceExit; t-reporter --forceExit;.bin/jest --testResultsProcessor
./node_modules/jest-juni FAIL test/app.test.js ✓ Updating a user
(45ms) ✓ Posting a new data (17ms) ✕ Getting all the user data
(10ms) ✕ Getting a single user data (4ms)
● Getting all the user data
expected 200 "OK", got 500 "Internal Server Error"
39 | //checking the get route
40 | test("Getting all the user data", async () => {
> 41 | const response = await request(app).get("/view").expect(200);
| ^
42 | expect(response.body.length).toBe(4);
43 | expect(md5(response.body)).toBe("f1d3ff8443297732862df21dc4e57262");
44 | });
at Object.<anonymous>.test (test/app.test.js:41:52)
----
at Test._assertStatus (node_modules/supertest/lib/test.js:252:14)
at node_modules/supertest/lib/test.js:306:17
at Test._assertFunction (node_modules/supertest/lib/test.js:285:13)
at Test.assert (node_modules/supertest/lib/test.js:164:23)
at Server.localAssert (node_modules/supertest/lib/test.js:120:14)
● Getting a single user data
expected 200 "OK", got 500 "Internal Server Error"
46 | //getting profile of a user based on id
47 | test("Getting a single user data", async () => {
> 48 | const response = await request(app).get("/view?id=2").expect(200);
| ^
49 | expect(response.body.length).toBe(1);
50 | expect(md5(response.body)).toBe("93b885adfe0da089cdf634904fd59f71");
51 | });
at Object.<anonymous>.test (test/app.test.js:48:57)
----
at Test._assertStatus (node_modules/supertest/lib/test.js:252:14)
at node_modules/supertest/lib/test.js:306:17
at Test._assertFunction (node_modules/supertest/lib/test.js:285:13)
at Test.assert (node_modules/supertest/lib/test.js:164:23)
at Server.localAssert (node_modules/supertest/lib/test.js:120:14)
Test Suites: 1 failed, 1 total Tests: 2 failed, 2 passed, 4
total Snapshots: 0 total Time: 1.612s Ran all test suites.
Force exiting Jest: Have you considered using --detectOpenHandles to
detect async operations that kept running after all tests finished?
We can see the last 2 tests ("Getting all the user data" and "Getting a single user data") did not pass. These 2 test the app.get('/view/:id?', function(req, resp) method.
How could I fix my app.get('/view/:id?', function(req, resp) method?
Update:
I fixed my app.js according to the suggestion of Yago Biermann, but it is still unable to pass the last 2 tests. My revised app.js:
const express = require("express");
const fs = require("fs");
//setting up the express router
const app = express();
app.use(express.json());
//write the code for routes here
app.post("/add", function(req, resp){
var jsonObject = req.body;
var jsonFile = fs.readFileSync("get.json", "UTF8");
var jsonArray = JSON.parse(jsonFile);
jsonArray.push(jsonObject);
jsonFile = JSON.stringify(jsonArray);
resp.json(jsonFile);
fs.writeFileSync("get.json",jsonFile,"utf-8");
});
app.get('/view', function(req, resp) {
const id = req.query.id;
var jsonFile = fs.readFileSync("get.json", "UTF8");
var data = JSON.parse(jsonFile);
// return the whole data if query parameter wasn't provided
if (!id) return resp.status(200).json(data)
// you should use find instead of every to get the user data
const user = data.find(user => {
if (user.id === id) {
return user;
};
return null;
});
// return the user otherwise return a 404 response
return user ? resp.status(200).json(user) : resp.status(404).json({message:"user not found"})
});
app.patch("/edit/:id", function(req, res){
let userID = req.params.id;
let userFile = fs.readFileSync("get.json", "UTF-8");
let userArray = JSON.parse(userFile);
let reqUserObject = req.body;
let newUserArray = userArray.map(user => {
if (user.id === userID) {
updatedUser = {...user, ...reqUserObject};
return updatedUser;
} else {
return user;
}
});
userFileData = JSON.stringify(newUserArray);
res.json(userFileData);
fs.writeFileSync("get.json", userFileData, "UTF-8");
});
module.exports = app;
And what I get when I run the tests:
rter --forceExit;es/.bin/jest --testResultsProcessor
./node_modules/jest-junit-repo FAIL test/app.test.js ✓ Updating a
user (43ms) ✓ Posting a new data (15ms) ✕ Getting all the user
data (9ms) ✕ Getting a single user data (4ms)
● Getting all the user data
expect(received).toBe(expected) // Object.is equality
Expected: 4
Received: 5
40 | test("Getting all the user data", async () => {
41 | const response = await request(app).get("/view").expect(200);
> 42 | expect(response.body.length).toBe(4);
| ^
43 | expect(md5(response.body)).toBe("f1d3ff8443297732862df21dc4e57262");
44 | });
45 |
at Object.<anonymous>.test (test/app.test.js:42:32)
● Getting a single user data
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: undefined
47 | test("Getting a single user data", async () => {
48 | const response = await request(app).get("/view?id=2").expect(200);
> 49 | expect(response.body.length).toBe(1);
| ^
50 | expect(md5(response.body)).toBe("93b885adfe0da089cdf634904fd59f71");
51 | });
52 |
at Object.<anonymous>.test (test/app.test.js:49:32)
Test Suites: 1 failed, 1 total Tests: 2 failed, 2 passed, 4
total Snapshots: 0 total Time: 1.71s Ran all test suites.
Force exiting Jest: Have you considered using --detectOpenHandles to
detect async operations that kept running after all tests finished?
The problem seems to be at your tests, in your test "Getting all the user data" you are making a request to /view but you don't have any route to view, at least it's not in your question, note that /view/:id is not the same as /view. Lastly, in your test "Getting a single user data" you are not providing the id for the user as a url parameter, instead you are passing it as a query parameter, so try to do the following:
test("Getting a single user data", async () => {
// pass the id as url
const response = await request(app).get("/view/2").expect(200);
expect(response.params.id).toBe(2);
expect(response.body.length).toBe(1);
expect(md5(response.body)).toBe("93b885adfe0da089cdf634904fd59f71");
});
See the docs about req.params. Hope I helped a bit!
Edit: As you said that the test file can't be changed, do the following in your view route:
// Change to view, now the test on route /view should work
app.get('/view', function(req, resp) {
const id = req.query.id;
var jsonFile = fs.readFileSync("get.json", "UTF8");
var data = JSON.parse(jsonFile);
// return the whole data if query parameter wasn't provided
if (!id) return resp.status(200).json(data)
// you should use find instead of every to get the user data
const user = data.find(user => {
if (user.id === id) {
return user;
};
return null;
});
// return the user otherwise return a 404 response
return user ? resp.status(200).json([user]) : resp.status(404).json({message:"user not found"})
});
I am currently working on a full-stack application using the MERN stack. I have been writing unit tests for each route's request handler, and have been finding some difficulty testing error cases, particularly in trying to stub a function to reject a promise
I have the relevant code shown below:
One of my endpoints. Request handling is delegated to userController
const express = require("express");
const { body } = require("express-validator");
const router = express.Router();
const userController = require("../../controllers/user");
router.post(
"/",
body("username")
.isLength({
min: 3,
max: 30,
})
.withMessage(
"Your username must be at least 3 characters and no more than 30!"
),
body("password")
.isLength({ min: 3, max: 50 })
.withMessage(
"Your password must be at least 3 characters and no more than 50!"
),
userController.createNewUser
);
The request handler for the above endpoint. I am trying to test createNewUser. I want to stub createNewUser so that it causes an error to be thrown, so I can test that a 500 status code response is sent.
const bcrypt = require("bcryptjs");
const { validationResult } = require("express-validator");
const User = require("../models/User");
exports.createNewUser = async (req, res, next) => {
const { username, password } = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array(),
});
}
try {
// Create a bcrypt salt
const salt = await bcrypt.genSalt(12);
// Hash the password
const hashedPassword = await bcrypt.hash(password, salt);
// Create a new user
const user = new User({
username,
password: hashedPassword,
});
const response = await user.save();
res.status(200).json(response);
} catch (err) {
res.status(500).json({ msg: err.message });
}
};
The unit test for User endpoints. I am unsure how to test the error case where a 500 status code is returned...
const request = require("supertest");
// const todosController = require("../controllers/todos");
const server = require("../server");
const User = require("../models/TodoItem");
const db = require("./db");
const agent = request.agent(server);
// Setup connection to the database
beforeAll(async () => await db.connect());
afterEach(async () => await db.clear());
afterAll(async () => await db.close());
describe("User endpoints test suite", () => {
describe("POST api/user", () => {
test("It should create a user successfully and return a 200 response code", async () => {
const response = await agent
.post("/api/user")
.set("content-type", "application/json")
.send({ username: "Bob", password: "12345" });
expect(response.body.username).toEqual("Bob");
expect(response.status).toBe(200);
});
});
});
When you are creating unit test, create something small first, you can add complexity and refactor later.
Below are example simple unit and integration tests based on your code.
You can start with user controller.
// File: user.controller.js
const bcrypt = require('bcryptjs');
exports.createNewUser = async (req, res) => {
try {
// Create a bcrypt salt.
const salt = await bcrypt.genSalt(12);
// Just make it simple, show the salt.
res.status(200).json(salt);
} catch (err) {
// Other wise, return the error message.
res.status(500).json({ msg: err.message });
}
};
Based on that try and catch, you can create unit test.
// File: user.controller.spec.js
const bcrypt = require('bcryptjs');
const user = require('./user.controller');
describe('User Controller', () => {
describe('create New User', () => {
const fakeJson = jest.fn();
const fakeStatus = jest.fn().mockReturnThis();
const fakeRes = {
status: fakeStatus,
json: fakeJson,
};
const spy = jest.spyOn(bcrypt, 'genSalt');
afterEach(() => {
jest.clearAllMocks();
});
it('should return salt', async () => {
const testSalt = 'salt';
// Mock the bcrypt.genSalt, always resolved with value testSalt.
spy.mockResolvedValue(testSalt);
// Call the function under test.
await user.createNewUser(undefined, fakeRes);
// Set the expectations.
expect(fakeStatus).toHaveBeenCalledWith(200);
expect(fakeJson).toHaveBeenCalledWith(testSalt);
expect(spy.mock.calls[0][0]).toBe(12);
});
it('should return error message when error', async () => {
const error = new Error('XXX');
// Mock the bcrypt.genSalt, always resolved with value testSalt.
spy.mockRejectedValue(error);
// Call the function under test.
await user.createNewUser(undefined, fakeRes);
// Set the expectations.
expect(fakeStatus).toHaveBeenCalledWith(500);
expect(fakeJson).toHaveBeenCalledWith({ msg: error.message });
expect(spy.mock.calls[0][0]).toBe(12);
});
});
});
When you run it on terminal:
$ npx jest user.controller.spec.js
PASS ./user.controller.spec.js
User Controller
create New User
✓ should return salt (5 ms)
✓ should return error message when error (1 ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
user.controller.js | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.511 s, estimated 1 s
Ran all test suites matching /user.controller.spec.js/i.
Next, if you have sure with your controller, you can create integration test with express.
For example you create app index like this.
// File: index.js
const express = require('express');
const userController = require('./user.controller');
const router = express.Router();
router.post('/user', (req, res, next) => userController.createNewUser(req, res, next));
const app = express();
app.use('/api', router);
module.exports = app;
You can test it using jest for normal & error case like this.
// File: index.spec.js
const request = require('supertest');
const bcrypt = require('bcryptjs');
const server = require('./index');
const userController = require('./user.controller');
const agent = request.agent(server);
describe('App', () => {
describe('POST /', () => {
// Create spy on bcrypt.
const spy = jest.spyOn(bcrypt, 'genSalt');
const error = new Error('XXX');
afterEach(() => {
jest.clearAllMocks();
});
it('should create a salt successfully and return a 200 response code', async () => {
// This test is slow because directly call bcrypt.genSalt.
// To make it faster, mock bcrypt completely, or use spy.mockResolvedValue('SALT');
// Send post request.
const response = await agent.post('/api/user');
// Make sure the response.
expect(response.status).toBe(200);
expect(response.type).toBe('application/json');
expect(spy.mock.results[0].value).toBeDefined();
const spyResult = await spy.mock.results[0].value;
expect(response.body).toBe(spyResult)
});
it('should return 500 and error message when catch error', async () => {
// Makesure spy reject.
spy.mockRejectedValue(error);
// Send post request.
const response = await agent.post('/api/user');
// Make sure the response.
expect(response.status).toBe(500);
expect(response.type).toBe('application/json');
expect(response.body).toBeDefined();
expect(response.body.msg).toBeDefined();
expect(response.body.msg).toBe(error.message);
});
// Or play around with another spy to error alternatives.
it('should return 404 when pass to next', async () => {
// Makesure createNewUser error.
jest.spyOn(userController, 'createNewUser').mockImplementation((req, res, next) => {
// You can setup res here or other implementation to check.
// For example, do next.
next();
});
// Send post request.
const response = await agent.post('/api/user');
// Make sure the response.
expect(response.status).toBe(404);
// Method bcrypt.genSalt should not get called.
expect(spy).not.toHaveBeenCalled();
});
});
});
When you run it from terminal:
$ npx jest index.spec.js
PASS ./index.spec.js
App
POST /
✓ should create a salt successfully and return a 200 response code (40 ms)
✓ should return 500 and error message when catch error (4 ms)
✓ should return 404 when pass to next (5 ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
user.controller.js | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.809 s, estimated 1 s
Ran all test suites matching /index.spec.js/i.
Note: You do not need to use sinon, jest provides mock functions.
I am new to jest, sorry if this is a trivial question but I went through the official jest docs and I am not able to find a solution to the problem.
I am developing a very simple app in nodejs that consumes data from a websocket and propagates it downstream to a set of consumers via zeromq.
The code is the following:
app.js:
const initializer = require("./dependencyInitializer");
const sock = initializer.zmqSock();
const ws = initializer.wsClient();
ws.on('update', data => {
sock.send([data.topic, JSON.stringify(data)]);
});
The websocket client is a class from a third party library extending from EventEmitter.
I would like to create a test asserting that the sock.send function is called exactly once inside the handler of the 'update' event.
This is my approach:
app.spec.js:
const ws = require("./app");
const initializer = require("./dependencyInitializer");
jest.mock("./dependencyInitializer", () => {
return {
wsClient: jest.fn(() => {
const EventEmitter = require("events")
const emitter = new EventEmitter()
return emitter;
}),
zmqSock: jest.fn(() => {
return {
send: jest.fn()
}
})
}
});
describe('on message received from websocket',() => {
it('should pass it to zmq', () => {
const data = {result: "ok"};
expect(initializer.wsClient).toHaveBeenCalledTimes(1);
expect(initializer.zmqSock).toHaveBeenCalledTimes(1);
const _sock = initializer.zmqSock();
const _ws = initializer.wsClient();
_ws.emit("update", data);
expect(_sock.send).toHaveBeenCalledTimes(1);
});
});
The test fails with the following:
on message received from websocket › should pass it to zmq
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
28 | const _ws = initializer.wsClient();
29 | _ws.emit("update", data);
> 30 | expect(_sock.send).toHaveBeenCalledTimes(1);
| ^
31 | });
32 | });
I am not sure if I am on the right path, I would like to understand what would be the best approach to develop a test like this.
Thanks
After mocking by jest.mock(), when the .wsClient() and .zmqSock() methods are called in the app.js file and app.spec.js file, the sock and ws objects in app.js are different with the app.spec.js.
{
wsClient: jest.fn(() => {
const EventEmitter = require("events")
const emitter = new EventEmitter()
return emitter;
})
}
Every time you call .wsClient(), it will create a new object.
Emitter can only listen for events from its own emit. The solution is to create the mock emitter and sock objects in the mock factory.
app.js:
const initializer = require('./dependencyInitializer');
const sock = initializer.zmqSock();
const ws = initializer.wsClient();
ws.on('update', (data) => {
sock.send([data.topic, JSON.stringify(data)]);
});
module.exports = { sock, ws };
app.test.js:
const app = require('./app');
const initializer = require('./dependencyInitializer');
jest.mock(
'./dependencyInitializer',
() => {
const EventEmitter = require('events');
const emitter = new EventEmitter();
const mSock = { send: jest.fn() };
return {
wsClient: jest.fn(() => emitter),
zmqSock: jest.fn(() => mSock),
};
},
{ virtual: true }
);
describe('on message received from websocket', () => {
it('should pass it to zmq', () => {
const data = { result: 'ok' };
expect(initializer.wsClient).toHaveBeenCalledTimes(1);
expect(initializer.zmqSock).toHaveBeenCalledTimes(1);
const _sock = initializer.zmqSock();
const _ws = initializer.wsClient();
// check if they have same reference
expect(app.sock).toBe(_sock);
expect(app.ws).toBe(_ws);
_ws.emit('update', data);
expect(_sock.send).toHaveBeenCalledTimes(1);
});
});
test result:
PASS examples/70024105/app.test.js (9.279 s)
on message received from websocket
✓ should pass it to zmq (2 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
app.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.112 s
I am pretty new to Nodejs and i am learning Nodejs course on udemy, I am facing some trouble of listen EADDRINUSE: address already in use :::4000 while re-running integration tests multiple time. The first time its successful but afterward I am getting the above-mentioned error on the following line
const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
I am pasting my index.js and two test files, if some can point me out it will be a great help for me.
Index.js
const Joi = require("#hapi/joi");
Joi.objectId = require("joi-objectid")(Joi);
const winston = require("winston");
const express = require("express");
const app = express();
require("./startup/logging")();
require("./startup/config")();
require("./startup/dbconnectivity")();
require("./startup/routes")(app);
const port = process.env.port || 4000;
const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
// exporting server object to be used in integration tests.
module.exports = server;
**Integration test file for Genre**
const request = require("supertest");
let server;
const {Genere} = require("../../models/genere");
const {User} = require("../../models/user");
describe("/api/genere", () => {
beforeEach(() => {
console.log("Before each Genre");
server = require("../../index");
});
afterEach(async () => {
console.log("After each Genre");
await Genere.deleteMany({});
await server.close();
});
describe("/GET", () => {
it("should return list of generes", async() => {
await Genere.insertMany([
{name: "genre1"},
{name: "genre2"},
{name: "genre3"}
]);
const res = await request(server).get("/api/geners");
expect(res.status).toBe(200);
console.log("response body is : " + res.body);
expect(res.body.length).toBe(3);
expect(res.body.map(g => g.name)).toContain("genre1");
});
});
describe("/GET/:id", () => {
it("should return genre with id", async() => {
const genre = new Genere({name: "genre1"});
await genre.save();
const res = await request(server).get("/api/geners/"+ genre.id);
expect(res.status).toBe(200);
expect(res.body.name).toBe("genre1");
});
it("should return error with invalid id", async() => {
const genre = new Genere({name: "genre1"});
await genre.save();
const res = await request(server).get("/api/geners/1");
expect(res.status).toBe(404);
expect(res.text).toMatch(/Invalid/);
});
});
describe("/POST", () => {
it("should return 401 if not authorized", async() => {
const genere = new Genere({name: "genere1"});
const res = await request(server).post("/api/geners").send(genere);
expect(res.status).toBe(401);
});
it("should return 400 if the name is less than 4 chars", async() => {
const res = await createRequestWithGenre({name: "g1"});
expect(res.status).toBe(400);
});
it("should return 400 if the name is greater than 25 chars", async() => {
const genreName = Array(26).fill("a").join("");
const res = await createRequestWithGenre({name: genreName})
expect(res.status).toBe(400);
});
it("should return 201 with gener object if proper object is sent", async() => {
const res = await createRequestWithGenre({name: "genre1"})
expect(res.status).toBe(201);
expect(res.body).toHaveProperty("_id");
expect(res.body).toHaveProperty("name", "genre1");
const genre = await Genere.find({ name: "genre1"});
expect(genre).not.toBe(null);
});
async function createRequestWithGenre(genre) {
const token = new User().generateAuthToken();
return await request(server)
.post("/api/geners")
.set("x-auth-token", token)
.send(genre);
}
});
});
As soon as i add another file for integration test like the one below i started to get the error which is mentioned after this file code.
const {User} = require("../../models/user");
const {Genere} = require("../../models/genere");
const request = require("supertest");
let token;
describe("middleware", () => {
beforeEach(() => {
console.log("Before each Middleware");
token = new User().generateAuthToken();
server = require("../../index");
});
afterEach(async () => {
console.log("After each Middleware");
await Genere.deleteMany({});
await server.close();
});
const exec = async() => {
return await request(server)
.post("/api/geners")
.set("x-auth-token", token)
.send({name: "gener1"});
}
it("should return 400 if invalid JWT token is sent", async() => {
token = "invalid_token";
const res = await exec();
expect(res.status).toBe(400);
expect(res.text).toBe("Invalid auth token");
});
});
Console Error
middleware
✕ should return 400 if invalid JWT token is sent (510ms)
● middleware › should return 400 if invalid JWT token is sent
listen EADDRINUSE: address already in use :::4000
10 | require("./startup/routes")(app);
11 | const port = process.env.port || 4000;
> 12 | const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
| ^
13 | // exporting server object to be used in integration tests.
14 | module.exports = server;
at Function.listen (node_modules/express/lib/application.js:618:24)
at Object.<anonymous> (index.js:12:20)
at Object.beforeEach (tests/integration/middleware.test.js:11:22)
If someone can help me why it fails on the multiple runs then it will be really helpful for me to understand why do we need to open and close server object every time.
Supertest is able to manage the setup/teardown of an express/koa app itself if you can import an instance of app without calling .listen() on it.
This involves structuring the code a little differently so app becomes a module, separate to the server .listen()
// app.js module
const app = require('express')()
require("./startup/logging")()
...
module.exports = app
Then the entrypoint for running the server imports the app then sets up the server with .listen()
// server.js entrypoint
const app = require('./app')
const port = process.env.port || 4000;
app.listen(port, () => {winston.info(`Listening on port ${port}`)});
When supertest uses the imported app, it will start its own server and listen on a random unused port without clashes.
// test
const request = require('supertest')
const app = require('./app')
request(app).get('/whatever')
The supertest "server" instance can be reused for multiple tests too
// reuse test
const supertest = require('supertest')
const app = require('./app')
describe('steps', () => {
const request = supertest(app)
it('should step1', async() => {
return request.get('/step1')
})
it('should step2', async() => {
return request.get('/step2')
})
})
One solution is to run jest with max workers specified to 1 which can be configured in your package.json in the following way:
"scripts": {
"test": "NODE_ENV=test jest --forceExit --detectOpenHandles --watchAll --maxWorkers=1"
},
If I understand your setup correctly, you have multiple intergration-test files which Jest will try to run in parallel (this is the default-mode). The error you're getting makes sense, since for each suite a new server instance is created before each test, but the server might already have been started while executing a different suite.
As described in the offical documentation instead of beforeEach it would make sense to use globalSetup where you would init your server once before running all test suites and stop the server afterwards:
// setup.js
module.exports = async () => {
// ...
// Set reference to your node server in order to close it during teardown.
global.__MY_NODE_SERVER__ = require("../../index");
};
// teardown.js
module.exports = async function() {
await global.__MY_NODE_SERVER__.stop();
};
// in your jest-config you'd set the path to these files:
module.exports = {
globalSetup: "<rootDir>/setup.js",
globalTeardown: "<rootDir>/teardown.js",
};
Alternatively you could run your tests with the --runInBand option and beforeAll instead of beforeEach in order to make sure that only one server is created before each test, but I'd recommend the first option.
I created this middleware which executing only once when any route in the website gets the first hit from a visitor:
// pg-promise
const db = require('./db/pgp').db;
const pgp = require('./db/pgp').pgp;
app.use(async (ctx, next) => {
try {
ctx.db = db;
ctx.pgp = pgp;
} catch (err) {
debugErr(`PGP ERROR: ${err.message}` || err);
}
await next();
});
// One-Time middleware
// https://github.com/expressjs/express/issues/2457
const oneTime = (fn) => {
try {
let done = false;
const res = (ctx, next) => {
if (done === false) {
fn(ctx, next);
done = true;
}
next();
};
return res;
} catch (err) {
debugErr(`oneTime ERROR: ${err.message}` || err);
}
};
const oneTimeQuery = async (ctx) => {
const result = await ctx.db.proc('version', [], a => a.version);
debugLog(result);
};
app.use(oneTime(oneTimeQuery));
This code executing on the first-time only when a user visiting the website, resulting:
app:log Listening on port 3000 +13ms
app:req GET / 200 - 24ms +2s
23:07:15 connect(postgres#postgres)
23:07:15 SELECT * FROM version()
23:07:15 disconnect(postgres#postgres)
app:log PostgreSQL 9.6.2, compiled by Visual C++ build 1800, 64-bit +125ms
My problem is that I want to execute it at the server start, when there's no any visit on the site.
The future purpose of this code will be to check the existence of tables in the database.
Solution:
Placing this in ./bin/www before the const server = http.createServer(app.callback()); declaration helped:
const query = async () => {
const db = require('../db/pgp').db;
const pgp = require('../db/pgp').pgp;
const result = await db.proc('version', [], a => a.version);
debugLog(`www: ${result}`);
pgp.end(); // for immediate app exit, closing the connection pool (synchronous)
};
query();
You could start your application using a js script that requires your app and uses node's native http module to fire up the server. Exactly like in koa-generator (click).
This is in your app.js file:
const app = require('koa')();
...
module.exports = app;
And then this is in your script to fire up the server:
const app = require('./app');
const http = require('http');
[this is the place where you should run your code before server starts]
const server = http.createServer(app.callback());
server.listen(port);
Afterwards you start your application with:
node [script_name].js
Of course keep in mind the async nature of node when doing it this way. What I mean by that - run the 'listen' method on 'server' variable in callback/promise.