Mocha + Nodejs + Heroku .env file - node.js

I have a REST app written in NodeJS running on Heroku. I have my .env file setup for local development and works fine whenever I run foreman to serve my app locally. The app also runs fine when I deploy it to my heroku server.
I am trying to write unit tests for my app with Mocha/Supertest/should/assert. When I run my app through Mocha, it doesn't load up the .env file to get my environment variables – in my case, the URL for the PSQL database. As a result, all my tests that involve DB I/O timeout.
I've been scouring the Internet for a solution but I can't seem to find anything helpful.
Here is some sample code:
app.js:
var application_root = __dirname,
express = require("express"),
port = process.env.PORT || 4482;
pg = require('pg').native,
client = new pg.Client(process.env.DATABASE_URL);
// Connect To DB
client.connect();
(...)
app.get('/api', function (req, res) {
res.send('PS API is running');
});
app.get('/', function (req, res) {
res.send('PS API is running');
});
(...)
// Read Users
app.get('/users', function (req,res) {
user.readUsers(res,client);
});
(...)
// Launch server
console.log('Listening on port: '+ port);
app.listen(port);
module.exports = app;
userTest.js
var request = require('supertest');
var assert = require('assert');
var app = require('app.js');
var should = require('should');
describe('Get /', function(){
it('should respond OK',function(done){
request(app)
.get('/')
.end(function(err, res){
res.status.should.equal(200);
done(err);
});
});
});
describe('Get /api', function(){
it('should respond OK',function(done){
request(app)
.get('/api')
.end(function(err, res){
res.status.should.equal(200);
done(err);
});
});
});
// Getting All Users
describe('Get /users', function(){
it('should respond OK',function(done){
request(app)
.get('/users')
.end(function(err, res){
res.status.should.equal(200);
done(err);
});
});
});
.env
== LOCAL DB ==
DATABASE_URL=MY_DB_URL
HEROKU_POSTGRESQL_GOLD_URL=MY_DB_URL
PATH=bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin
And the output I get from running mocha test
Listening on port: 4482
․․Getting all users
․
2 passing (2 seconds)
1 failing
1) Get /users should respond OK:
Error: timeout of 2000ms exceeded
at Object.<anonymous> (/usr/local/lib/node_modules/mocha/lib/runnable.js:165:14)
at Timer.list.ontimeout (timers.js:101:19)
When I replace the process.env.DATABASE_URL with my hardcoded PSQL local URL, the tests all pass. So it's clear that the .env file is not being read by mocha.
I have also tried passing env vars to Mocha with little success. Does anyone know of a proper way to have Mocha read in my environment vars from the .env file?

The foreman gem (written by a Heroku engineer, to make it easy to use .env files and Procfiles in development as you would on Heroku production) has a command just for this purpose: run.
foreman run npm test <-- or however you trigger your tests.
Alternatively, on my current project here's what we do:
we have a test.env file containing the environmental variables and values appropriate for testing, in Unix export format. So, yes, export DATABASE_URL=MY_DB_URL. The format is a bit different, but that's an annoyance we're OK with
We have a Makefile containing the following directives:
-include test.env
test:
npm test
When we want to run the tests for our project we just make test. Make will load up the test.env file, assign all the environmental variables, then run npm test for us.

I'm not entirely sure, but I don't think you can make mocha read your .env file. That appears to be specific to foreman. If your environment variable definitions are plain KEY=VALUE, then I believe something as simple as env $(cat .env) mocha would do. Otherwise, you may have to do some preprocessing first using sed/perl/etc.

After searching the internet for days, following is something, that's working for me:
> PATH=$(npm bin):$PATH env $(cat .env) mocha
It is to be noted that PATH=$(npm bin):$PATH is used as my environment was not able to find mocha.

Related

Jest and supertest: Test keep exceeding timeout

Hello I am a bit confused by this error I have encountered.
I am working on an Universal React App using Webpack 5 and Express.
I want to implement Jest support by using the React-testing-Library for the frontend (which work) and supertest for the backend (this is where I am blocked).
I am following this basic tutorial recommended by the jest doc himself in order to use jest on an node express environment.
But everytime I get this error:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
Here are my code:
server.js
import app from './app.js';
import { mongooseConnection, disconnectMongoDB } from "./routers/services/url/urlDB.js"; // we call mongooseConnect var to connect only once into the mongoDB database
const PORT = process.env.PORT || 8080;
// the server listen on the port set by node on localhost.
app.listen(PORT, () => {
console.log(
`Server listening on \x1b[42m\x1b[1mhttp://localhost:${PORT}\x1b[0m in \x1b[41m${process.env.NODE_ENV}\x1b[0m`,
);
});
// when when we shut down the app we execute a callback function before closing the server
process.on('exit', function() {
disconnectMongoDB();
});
app.js
import express from 'express';
import path from 'path';
import cors from 'cors';
import {envIsProduction, envIsDevelopment} from './envmode/envUtil.js';
import { enableHMR } from './reload/hotReload.js';
let app = express();
// if we have set the environnent on production then:
if (envIsProduction()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( PRODUCTION ) |");
console.log("|_______________________________________|");
console.log(" ");
app.use(express.static(path.join(__dirname,'../client'))); // we serve static file like the bundle-app.js to the browser from the current directory where the server is executed and we move to the top root to access the file
}
else if (envIsDevelopment()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( DEVELOPMENT ) |");
console.log("|_______________________________________|");
console.log(" ");
enableHMR(app); // we enable the Hot MPodule Reload on the frontend and the backend
}
app.use(cors());
app.use(express.urlencoded({extended:false}));
app.use(express.json());
//Hot reload!
//ALL server routes are in this module!
app.use((req, res, next) => {
require("./routers/routers")(req, res, next);
});
export default app;
routers.js
import renderPage from "./renderpage/renderPage.js";
import { serverRoutes, reactRouterRoutes, getReactRouterRoutesString } from "./routes.js";
import express from "express";
import routerLoginDB from "./request/routerLoginDB.js";
import routerSignupDB from "./request/routerSignupDB.js";
const router = express.Router();
// Put all your server routes in here
// When the user connect to the root of the server we send the page
router.get(serverRoutes.root, renderPage);
// When the user send a get request by the /click route a console.log and a respone is send.
router.get(serverRoutes.click, (req, res)=>{
res.status(200).send("Click");
});
// when this user want to login into his account, we ask for the routerLoginDB to handle it
router.post(serverRoutes.login,routerLoginDB);
// when this user want to signup into his account, we ask for the routerSignupDB to handle it
router.post(serverRoutes.signup, routerSignupDB);
// For all the routes that only react-router need to use, if we refresh on a nested route of the react-router from the client side then we redirect it to the root route "/"
router.get(reactRouterRoutes,(req,res) => {
res.redirect("/");
});
router.get("*", (req,res) =>{
res.status(404).send('page not found');
}); //For all other type of request excluding the one specified here, we send back a 404 page;
module.exports = router;
app.test.js
import request from '../utils/test-node-utils.js'
describe("Test the /click path", () => {
test("It should response the GET method", () => {
return request
.get("/click")
.expect(200);
});
});
and finally test-node-utils.js
import supertest from "supertest";
import app from "../serverside/app.js";
const request = supertest(app);
export default request;
Don't believe what the error say because I think it is more deep than that.
I have tried to increased the jest timeout value but it keep being stuck and reach the timeout limit.
I have done exactly like the tutorial say without using my project structure and it worked but when I try to implement the tutorial in my backend structure, it don't work with supertest.
I think it is related to my files or backend structure that make it don't work with the test.
Thanks in advance for your help
I've recently debugged a similar issue where my Jest tests would run successfully (or not) in my dev. environment but when I would try and package the app as a Docker image all my tests would time out.
It turned out that by commenting out the line which setup CORS, which for me I only turned on in production builds (should have been a clue), the tests started to run again when building the image.
...
const NODE_ENV = process.env.NODE_ENV;
const app = express();
NODE_ENV.toUpperCase() === 'PRODUCTION' && app.use(cors);
...
I mentioned this as I can see from your snippet above that you are also using the cors middleware and that, in your case, it's set all the time.
Perhaps not your issue, and you may want CORS in your tests for some reason, but try commenting it out and see if your tests run.

Cypress.io can't find the specs when wrapping with nodejs

I am trying to add a REST API on top of Cypress.io to mimic the Appium framework. I can run the test from the console, but when I try to run the spec with an http request cypress can't find the test. I've tried to add a bunch of different path settings in cypress.json located in the root folder of the project but no luck so far.
Here is a small example server of what I'm trying to achieve.
import express from 'express';
import cypress from 'cypress';
const app = express();
const PORT = 5000;
app.get('/cypress/v1/run/:specName', (req, res) => {
cypress.run({
spec: req.params.specName})
.then(results => {
res.status(200).send({
status: results.totalFailed,
result: results
})
})
.catch((err) => {
console.error(err)
});
});
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`)
});
Whatever I send in I get following error:
Request: curl -v http://localhost:5000/cypress/v1/run/test.js
Response: {"result":{"failures":1,"message":"Could not find Cypress test run results"}}
Log:
Can't run because no spec files were found.
We searched for any files matching this glob pattern:
test.js
So my question is, what am I missing?
Cypress seems to be extremely sensitive to configuration and is somewhat not logic. I changed the config file to following and specs are found:
{
"projectId": "xyz123",
"integrationFolder": "cypress/integration",
"testFiles": "**/*.*"
}
I still have to specify the test as cypress/integration/test.js, otherwise Cypress will not find it.

How to startup the server when testing an Express app via Mocha

I would like to write unit tests using Mocha for my Nodejs/Express app that I have written in visual studio. I have scoured everywhere I could looking for a simple tutorial but not found what I am looking for. I have seen many tutorials in creating a test using assert to test that 5=5, etc. but that's not what I want to do.
I am trying to add a JavaScript Mocha Unit Test file through VS and then all I really want it to do is open the home page of my app, check for some content in the body and pass the test. If I want to run the tests from the Test Explorer window the nodejs app can't be running and if it isn't running there would be nothing to receive the request for the homepage.
So I'm not sure if the test itself is somehow supposed to launch the app or what? I feel like I'm in a catch 22 and missing the very basics, just don't see it described anywhere.
What you're looking for is most commonly called an API test - a part of integration testing, not a unit test. If a test touches network, a database or I/O it's, most commonly, an integration test instead.
Now to your question. In order to test your app.js code without starting up the server manually beforehand you can do the following:
module.export your app server.
In your tests, use chai-http to test routes.
require your app in the tests and use that instead of URL's when testing routes.
The key here is the 1st bullet point. You must export your app so you can require it and use it in your tests. This allows you to skip the part where you start a separate server process to run the tests on.
Server code
// app.js
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.json())
// Routes
app.post('/register', (req, res) => {
const requiredFields = ['name', 'email']
if (requiredFields.every(field => Object.keys(req.body).includes(field))) {
// Run business logic and insert user in DB ...
res.sendStatus(204)
} else {
res.sendStatus(400)
}
})
app.listen(3000)
// export your app so you can include it in your tests.
module.exports = app
Test code
// test/registration.spec.js
const chai = require('chai')
const chaiHttp = require('chai-http')
// `require` your exported `app`.
const app = require('../app.js')
chai.should()
chai.use(chaiHttp)
describe('User registration', () => {
it('responds with HTTP 204 if form fields are valid', () => {
return chai.request(app)
.post('/register')
.send({
name: 'John Doe',
email: 'john#doe.com'
})
.then(res => {
res.should.have.status(204)
})
.catch(err => {
throw err
})
})
it('responds with HTTP 400 if some fields are missing', () => {
return chai.request(app)
.post('/register')
.send({
name: 'John Doe'
})
.catch(err => {
err.should.have.status(400)
})
})
})
Then just run your test from the root directory with:
$ mocha test/registration.spec.js

How to test Login API NodeJs using Mocha and Chai

I am new to Test driven development and I want to test my login API but I cant seem to understand fully how to implement tests with Database and what is the proper way to do it?
First, I am also not an expert in this topic but i have been using this method for quite some time. If anyone find that what i'm writing is wrong or somewhat misleading, please correct me. I am very open to critics and opinions.
As the name suggests, TDD method requires you to write the test before the implementation. Basically, you write the test, see it's failing, write the implementation and repeat until the test is passed.
If you are using express, you may want to use the supertest module. They way to use it is similar to superagent. You can install it by running
npm install supertest --save-dev
I am going to show you a very simple example of how to use it with mocha and chai.
So here's an example of express app:
// file: app.js
const express = require('express');
const app = express();
// your middlewares setup goes here
const server = app.listen(8000, () => {
console.log('Server is listening on port 8000');
});
module.exports = app;
And here's the example test case of the login API:
// file: test/api.js
const request = require('supertest');
const app = require('../app');
const expect = require('chai').expect;
describe('Login API', function() {
it('Should success if credential is valid', function(done) {
request(app)
.post('/api/v1/login')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send({ username: 'username', password: 'password' })
.expect(200)
.expect('Content-Type', /json/)
.expect(function(response) {
expect(response.body).not.to.be.empty;
expect(response.body).to.be.an('object');
})
.end(done);
});
});
You may run it with this command
node_modules/mocha/bin/mocha test/**/*.js
The example above assumes that you will implement the login API using POST method at /api/v1/login path. It also assumes that you will receive and respond data with json format.
What the example test case does that it tries to send a POST request to /api/v1/login with the following data:
{
username: 'username',
password: 'password'
}
Then, it expects that your API will respond with 200 response code as shown in this line:
.expect(200)
If it receive a response with code other than 200, the test will fail.
Then, it expect that the Content-Type of your response to be application/json. If the expectation does not meet the reality, the test will also fail.
This code below:
.expect(function(response) {
expect(response.body).not.to.be.empty;
expect(response.body).to.be.an('object');
})
It checks the response from your server. You can use the chai's expect inside the function body as shown above. You may notice that supertest also provide expect method. But, the way to use both supertest's expect and chai's expect is different.
And finally, call end function with done callback so that the test case can be run properly.
You may want to check supertest documentation to get more details on how to use it.
Establishing database connection before testing
If you need to maintain a database connection before running all the test case, here's the idea:
Create another file inside the test directory. For example, database_helper.js. Then, write the following code:
before(function(done) {
// write database connection code here
// call done when the connection is established
});
I've tried it with mongoose before, and it worked for me.
I hope that helps.

test express API requires local server

I've read i can run mocha test in an Express application (nodeJS) with super test and therefore it is not required to run the app in a different terminal session.
Whatever i try it always ends with a connection error.
To configure our continuous integration it is evident the integration and unit test (mocha, supertest, should) should be able to run without the node server is also running
The written tests are to validate our app's internal api end points
Who can explain how to run the tests without running the express server of the app so they can be integrated with for example strider
You need to split out your production code that calls app.listen and make sure that does not get executed during a mocha test run. I put all of my routes and settings code in app/index.js and then have a separate file app/server.js that has just a tiny bit of code to start listening, connect to the database, etc. But most of my application details are configured in app/index.js so I can test them with supertest.
//index.js
var app = require("express")();
app.get("/", function (req, res) {
res.type("text");
res.send("Welcome home");
});
module.exports = app;
//server.js
#!/usr/bin/env node
var app = require("./index");
app.listen(3000);
//index.mocha.js
var app = require("./index");
var request = require("supertest")(app);
var expect = require("expectacle");
describe("the home page", function () {
it("should welome me in plain text", function(done) {
request.get("/")
.expect(200)
.expect("Content-Type", "text/plain; charset=utf-8")
.end(function (error, result) {
expect(error).toBeFalsy();
expect(result.text).toBe("Welcome home");
done();
});
});
});

Resources