jest asynchronous test ignoring expect() function - javascript

server.js is a simple express.js file that uses jwt tokens. I currently want to test a simple route that will only return the string "Hello World" as shown below
app.get("/", (req, res) => {
res.send("Hello World");
});
The code below is my jest file that is using supertest to send the request along with the valid jwt token
const supertest = require("supertest");
let server, request;
const serverStart = new Promise(resolve => {
server = require("../server.js");
request = supertest(server);
server.on("app_started", () => {
resolve();
});
});
beforeAll(async () => {
await serverStart;
});
afterAll(() => {
server.close();
});
describe("When testing the server.js", () => {
it("Should connect successfully and be able to return a response", async () => {
const response = await request
.get("/")
.set("Authorization", `bearer ${process.env.AUTHTOKEN}`);
expect(response.text).toBe("Hello World");
console.log(response.text);
});
});
When running this jest (after it's timeout of 5 seconds) says Timeout - Async callback was not invoked within the 5000ms timeout specified however, the console.log that I have added after the expect function outputs "Hello World" to the console meaning the request is made and returns a value but it carriers on with the code but is just skipping the expect function
I've also tried this with done() and also using a then() but got the same error both times and I've console logged the time before and after the call and found it only takes a few milliseconds to return a value, so why does the expect not seem to complete the test?

Pretty sure your problem is the app_started event that you are listening to. I don't know where that event is documented. I think you should use listening instead. I'm going to make some assumptions about your server.js file.
The following test passes. I think your tests never actually start because you are listening for an event that will never be fired.
This is the server.js file that I am testing with:
const http = require("http");
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send("hello");
});
const server = http.createServer(app);
server.listen(8081);
module.exports = server;
This is my test file server.test.js:
const supertest = require("supertest");
let server, request;
const serverStart = new Promise(resolve => {
server = require("./server.js");
request = supertest(server);
server.on("listening", () => resolve());
});
beforeAll(async () => {
await serverStart;
});
afterAll(() => {
server.close();
});
describe("server", () => {
it("should get hello", async () => {
const response = await request.get("/");
expect(response.text).toBe("hello");
});
});

User zero298 pointed out that the test was failing during the beforeAll() function because server.on() wasn't returning anything. In the end I wrote the promise inside the server.js which resolves after it has started and then exported this promise.
let server;
const serverStart = new Promise(resolve => {
server = app.listen(port, err => {
if (err) {
console.log(err);
}
console.log("Listening on port " + port);
resolve();
});
});
module.exports = app;
module.exports.close = () => server.close();
module.exports.serverStart = serverStart;
and the beforeAll() in server.test.js now looks like
const server = require("../server.js");
let request;
beforeAll(async () => {
await server.serverStart;
request = supertest(server);
});

Related

How to test with Jest after connecting to MongoDB?

I'm trying to set up testing for various routes in my Express server that require connectivity to my MongoDB database.
I'm not sure how to structure the Jest file in order to allow for testing. In my normal index.js file, I'm importing the app, and running app.listen within the connect .then call, like this:
const connect = require("../dbs/mongodb/connect");
connect()
.then(_ => {
app.listen(process.env.PORT, _ => logger.info('this is running')
})
.catch(_ => logger.error('The app could not connect.');
I've tried running the same setup in my test.js files, but it's not working.
For example:
const connect = require("../dbs/mongodb/connect");
const request = require("supertest");
const runTests = () => {
describe("Test the home page", () => {
test("It should give a 200 response.", async () => {
let res = await request(app).get("/");
expect(res.statusCode).toBe(200);
});
});
};
connect()
.then(_ => app.listen(process.env.PORT))
.then(runTests)
.catch(err => {
console.error(`Could not connect to mongodb`, err);
});
How is it possible to wait for a connection to MongoDB before running my tests?
So, turns out there were a few changes that I had to make. Firstly, I had to load in my .env file before running the tests. I did this by creating a jest.config.js file in the root of my project:
module.exports = {
verbose: true,
setupFiles: ["dotenv/config"]
};
Then within the actual testing suite, I'm running beforeEach to connect to the MongoDB server.
const connect = require("../dbs/mongodb/connect");
const app = require("../app");
const request = require("supertest");
beforeEach(async() => {
await connect();
});
describe("This is the test", () => {
test("This should work", async done => {
let res = await request(app).get("/home");
expect(res.statusCode).toBe(200);
done();
})
});

Merge Two codes

I have 2 files in Node js .I want to merge these 2, but I am facing problem..
This file calls function from python file
const app = express()
let runPy = new Promise(function(success, nosuccess) {
const { spawn } = require('child_process');
const pyprog = spawn('python', ['./ml.py']);
pyprog.stdout.on('data', function(data) {
success(data);
});
pyprog.stderr.on('data', (data) => {
nosuccess(data);
});
});
app.get('/', (req, res) => {
res.write('welcome\n');
runPy.then(function(testMLFunction) {
console.log(testMLFunction.toString());
res.end(testMLFunction);
});
})
app.listen(4000, () => console.log('Application listening on port 4000!'))
python file ml.py
def testMLFunction():
return "hello from Python"
print(testMLFunction())
Below file works on button click with post method
var fs = require('fs');
var server = http.createServer(function (req, res) {
if (req.method === "GET") {
res.writeHead(200, { "Content-Type": "text/html" });
fs.createReadStream("./form.html", "UTF-8").pipe(res);
} else if (req.method === "POST") {
var result = "";
req.on("data", function (chunk) {
console.log(chunk.toString());
result = chunk;
//body=body.toUpperCase;
});
req.on("end", function(){
res.writeHead(200, { "Content-Type": "text/html" });
res.end(result);
});
}
}).listen(3000);
how can I do that..
There are several things wrong here. I will explain as plain as possible.
You forgot to add in your code var express = require('express')
The promise you made, runPy, must be wrapped in a function, whereas your approach will instantly start the promise upon loading the script itself.
You are resolving/rejecting on first incoming output, you shouldn't do that because you won't be able to know what really happened in the shell. You need to store those output lines, this is the only way of you knowing what the script tells you.
In runPy you must resolve/reject upon pyprogr close event.
You cannot access directly the method of another script, no matter what that kind of file that is a py, sh, bat, js. However, you can access internal functions of it by passing arguments to the shell, and of course, that script must have the logic required to deal with those arguments.
When using spawn/exec you must keep in mind that YOU ARE NOT the user executing the script, the node user is, so different outcomes may occur.
Most importantly, your targeted script must PRINT/ECHO to shell, no returns! The best approach would be to print some json string, and parse it in javascript after the shell is closed, so you can have access to an object instead of a string.
Below you will find a demo for your use case, i changed the python file so it can print something.
ml.py
print('I\'m the output from ml.py')
index.js
const express = require('express');
const app = express()
let runPy = function () { // the promise is now wrapped in a function so it won't trigger on script load
return new Promise(function (success, nosuccess) {
const {spawn} = require('child_process');
const pyprog = spawn('python', ['./ml.py'], {shell: true}); // add shell:true so node will spawn it with your system shell.
let storeLines = []; // store the printed rows from the script
let storeErrors = []; // store errors occurred
pyprog.stdout.on('data', function (data) {
storeLines.push(data);
});
pyprog.stderr.on('data', (data) => {
storeErrors.push(data);
});
pyprog.on('close', () => {
// if we have errors will reject the promise and we'll catch it later
if (storeErrors.length) {
nosuccess(new Error(Buffer.concat(storeErrors).toString()));
} else {
success(storeLines);
}
})
})
};
let path = require('path');
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // you need to set this so you can catch POST requests
app.all('/', (req, res) => { // i've change this from .get to .all so you can catch both get and post requests here
console.log('post params', req.body);
if(req.body.hasOwnProperty('btn-send')){
runPy()
.then(function (pyOutputBuffer) {
let message = 'You sent this params:\n' +JSON.stringify(req.body, null,2) + '\n';
message += Buffer.concat(pyOutputBuffer).toString();
res.end(message);
})
.catch(console.log)
}else{
res.sendFile(path.join(__dirname,'form.html')); // you need an absolute path to 'file.html'
}
});
app.listen(4000, () => console.log('Application listening on port 4000!'));
form.html
<div>hello there</div>
<form action="/" method="post">
<input type="text" value="" name="some-text"/>
<button type="submit" value="1" name="btn-send" >Press me!</button>
</form>

How to write integration test with Jest and Express?

I am trying to write integration tests with a third party microservice (Assume it is call Service). When a request is sent to Service, it will fire a webhook. What I am trying to do is to test it the webhook parsed correctly.
import express from "express";
import bodyParser from "body-parser";
import { handleHook} from "./hook";
export const app = express();
app.use(bodyParser.json(), handleHook);
describe("integration", () => {
test("hook", async () => {
const mock = jest.fn();
app.post("/", (req, res) => {
mock(req);
res.send("");
});
const p = new Promise<Server>(resolve => {
const server = app.listen(3000, () => {
resolve(server);
});
});
const server = await p;
await upload();
jest.useFakeTimers();
setTimeout(() => {
server.close();
}, 15000);
jest.runAllTimers();
expect(mock).toHaveBeenCalledTimes(4);
});
});
However, I got two problems. First, I got
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue
but using --detectOpenHandles does not show how to troubleshoot this issue.
Second, because the webhook is fire asynchronously, how can I ensure to catch all of them or wait for a period of time to timeout.

Unit Testing Controllers use Jest, NodeJS

I want to check a case that certain routes are calling the correct controller use Jest specific (mock or spy).
It is case specific for unit testing. Somebody can help me how to check it use jest. I don't need verify kind of
expect (status code or res object) i need to check if controller have been called.
Thanks!
For instance:
// todoController.js
function todoController (req, res) {
res.send('Hello i am todo controller')
}
// index.spec.js
const express = require('express');
const request = require('request-promise');
const todoController = require('./todoController');
jest.mock('./todoController');
const app = express();
app.get('/todo', todoController)
test('If certain routes are calling the correct controller , controller should to have been called times one.', async() => {
await request({url: 'http://127.0.0.1/todo'})
expect(todoController).toHaveBeenCalledTimes(1);
})
Actually if you search, there are many references out there.
In the following, I share a few ways that I know.
One of the big conceptual leaps to testing Express applications with mocked request/response is understanding how to mock a chained
API eg. res.status(200).json({ foo: 'bar' }).
First you can make some kind of interceptor, this is achieved by returning the res instance from each of its methods:
// util/interceptor.js
module.exports = {
mockRequest: () => {
const req = {}
req.body = jest.fn().mockReturnValue(req)
req.params = jest.fn().mockReturnValue(req)
return req
},
mockResponse: () => {
const res = {}
res.send = jest.fn().mockReturnValue(res)
res.status = jest.fn().mockReturnValue(res)
res.json = jest.fn().mockReturnValue(res)
return res
},
// mockNext: () => jest.fn()
}
The Express user-land API is based around middleware. AN middleware that takes a request (usually called req), a response (usually called res ) and a next (call next middleware) as parameters.
And then you have controller like this :
// todoController.js
function todoController (req, res) {
if (!req.params.id) {
return res.status(404).json({ message: 'Not Found' });
}
res.send('Hello i am todo controller')
}
They are consumed by being “mounted” on an Express application (app) instance (in app.js):
// app.js
const express = require('express');
const app = express();
const todoController = require('./todoController');
app.get('/todo', todoController);
Using the mockRequest and mockResponse we’ve defined before, then we’ll asume that res.send() is called with the right payload ({ data }).
So on your test file :
// todo.spec.js
const { mockRequest, mockResponse } = require('util/interceptor')
const controller = require('todoController.js')
describe("Check method \'todoController\' ", () => {
test('should 200 and return correct value', async () => {
let req = mockRequest();
req.params.id = 1;
const res = mockResponse();
await controller.todoController(req, res);
expect(res.send).toHaveBeenCalledTimes(1)
expect(res.send.mock.calls.length).toBe(1);
expect(res.send).toHaveBeenCalledWith('Hello i am todo controller');
});
test('should 404 and return correct value', async () => {
let req = mockRequest();
req.params.id = null;
const res = mockResponse();
await controller.todoController(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({ message: 'Not Found' });
});
});
This is only 1 approach to testing Express handlers and middleware. The alternative is to fire up the Express server.

Travis tests returning undefined

I'm testing with jasmine and it's working fine locally. However Travis CI is returning undefined for all the API tests. Example
4) Server GET /api/v1/orders Status 200
Message:
Expected undefined to be 200.
Stack:
Error: Expected undefined to be 200.
at
Snippet from the tests
describe('GET /api/v1/orders', function () {
var data = {};
beforeAll(function (done) {
Request.get('http://localhost:3001/api/v1/orders', function (error, response, body) {
data.status = response.statusCode;
data.body = JSON.parse(body);
data.number = data.body.length;
done();
});
});
it('Status 200', function () {
expect(data.status).toBe(200);
});
it('It should return three Items', function () {
expect(data.number).toBe(3);
});
});
Could the problem be from the 'http://localhost:3001/api/v1/orders' URL?
You don't seem to be starting your server anywhere so localhost:3001 isn't available.
A good solution would be to use something like supertest. It would allow you to do something like so:
app.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
const routes = require('./api/routes/routes.js')(app);
// We listen only if the file was called directly
if (require.main === module) {
const server = app.listen(3001, () => {
console.log('Listening on port %s...', server.address().port);
});
} else {
// If the file was required, we export our app
module.exports = app;
}
spec.routes.js
'use strict';
var request = require('supertest');
var app = require('../../app.js');
describe("Test the server", () => {
// We manually listen here
const server = app.listen();
// When all tests are done, we close the server
afterAll(() => {
server.close();
});
it("should return orders properly", async () => {
// If async/await isn't supported, use a callback
await request(server)
.get('/api/v1/orders')
.expect(res => {
expect(res.body.length).toBe(3);
expect(res.status).toBe(200);
});
});
});
Supertest allows you to make requests without relying on a specific port/url/other.

Categories