Node.js with Express, Jest and SuperTest never fail - node.js

I'm new in Jest and TDD. Please, help me!
I'm using supertest to request the API, but even with the server turned off, the tests never fail. I've tried use return or async await and it not solved
I have the following structure at Node.js project:
nodemodules
src
controllers
users-controller.js
index.js
routes.js
server.js
test
user.test.js
package.json
package.json:
"scripts": {
"test": "jest",
"lint": "eslint src/** test/** --fix",
"start": "node src/server.js",
"jest-watch": "jest --watch"
},
"devDependencies": {
"eslint": "^6.8.0",
"jest": "^25.3.0",
"supertest": "^4.0.2"
},
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1"
}
src/server.js:
const app = require('./index')
app.listen(3001, () => {
console.log('Server running on port 3001')
})
src/index.js:
const express = require('express')
const routes = require('./routes')
const app = express()
app.use(express.json())
app.use(routes)
module.exports = app
src/routes.js
const routes = require('express').Router()
const UserController = require('./controllers/users-controller')
routes.get('/', (req, res) => { res.status(200).send() })
routes.get('/users', UserController.findAll)
routes.post('/users', UserController.create)
module.exports = routes
src/controllers/user-controller.js
module.exports = {
findAll(req, res) {
const users = [
{ name: 'John Doe', mail: 'john#mail.com' }
]
return res.status(200).json(users)
},
create(req, res) {
return res.status(201).json(req.body)
}
}}
test/user.test.js:
const request = require('supertest')
const app = require('../src/index')
test('Should list users', () => {
return request(app).get('/users')
.then(res => {
expect(res.status).toBe(200)
expect(res.body).toHaveLength(1)
expect(res.body[0]).toHaveProperty('name', 'John Doe')
})
})
test('Should insert a user', async () => {
await request(app).post('/users')
.send({ name: 'Walter Mitty', email: 'walter#mail.com' })
.then(res => {
expect(res.status).toBe(201)
expect(res.body.name).toBe('Walter Mitty')
})
})
And the result is always the same:
PASS test / user.test.js
✓ Should list users. (16ms)
✓ Should insert a user. (13ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.456s, estimated 1s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.

Oh I see what the problem is, although isn't really a problem, is the normal behavior of Jest and Supertest. I'll explain to you. If Express app isn't listening, it doesn't affect the Jest or Supertest behavior. Why? Just because the app that you pass to supertest request(app) runs an independent process of the app, and when the tests are finished, that app finishes too. In other words, Supertest runs the Express app on a different process, do the tests, and finishes that process.
This test will fail if you expect another response code, for example 400. The following test should fail for sure:
test('Should list users', async () => {
// First assertion
const response = await request(app)
.get('/users')
.send()
.expect(400) // This will make fail, because the app instance running for test, is working just fine and will return 200, if you expect a different code, so the test will fail
// Second assertion
expect(response.body[0]).toHaveProperty('name', 'John Doe')
})
So that is the normal behavior of Jest and Supertest. It runs the Express app, just for running the tests, in another independent process. It doesn't matter if your Express main process is still running or if it's stopped.

Related

Integration test failure using Puppeteer

I am new to Node.JS and very curious to learn more about it, therefore I decided to do some exercises from a book.
The point which I am struggling is with the integration test.
I would like to have a crawler checking my application to see if the links are working fine. For that I am using the following code:
package.json
{
"main": "meadowlark.js",
"scripts": {
"test": "jest",
"lint": "eslint meadowlark.js lib"
},
"dependencies": {
"express": "^4.17.1",
"express3-handlebars": "^0.5.2"
},
"devDependencies": {
"eslint": "^5.15.3",
"jest": "^24.9.0",
"portfinder": "^1.0.20",
"puppeteer": "^1.13.0"
}
}
integration-tests/basic-navigation.test.js
const portfinder = require('portfinder')
const puppeteer = require('puppeteer')
const app = require('../meadowlark.js')
let server = null
let port = null
beforeEach(async () => {
port = await portfinder.getPortPromise()
server = app.listen(port)
})
afterEach(() => {
server.close()
})
test('home page links to about page', async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto(`http://localhost:${port}`)
await Promise.all([
page.waitForNavigation(),
page.click('[data-test-id="about"]'),
])
expect(page.url()).toBe(`http://localhost:${port}/about`)
await browser.close()
})
meadowlark.js
// Starting an express application
var express = require('express');
var app = express();
/* eslint-disable no-undef */
const port = process.env.PORT || 3000
/* eslint-enable no-undef */
// Set up handlebars view engine (Templating)
// Check the views folder for html skeleton and the respective
// handlebars
var handlebars = require('express3-handlebars')
.create({ defaultLayout:'main' });
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
/* eslint-disable no-undef */
app.set('port', process.env.PORT || 3000);
/* eslint-enable no-undef */
const handlers = require('./lib/handlers')
// Function to generate the quote of the day
//const fortune = require('./lib/fortune')
// Homepage
app.get('/', handlers.home)
// About
app.get('/about', handlers.about);
// 404
app.use(handlers.notFound);
// 500
app.use(handlers.serverError)
// Binding to the port
if(require.main === module) {
app.listen(port, () => {
console.log( `Express started on http://localhost:${port}` +
'; press Ctrl-C to terminate.' )
})
} else {
module.exports = app
}
Error
meadowlark/integration-tests/basic-navigation.test.js:9
beforeEach(async () => {
^
ReferenceError: beforeEach is not defined
What am I missing/ doing wrong here?
You need to run your test through jest and not plain node otherwise all the globals defined by jest won't exist.
Example if you're using yarn:
yarn jest to run all tests it can find based on jest default settings (see documentation to customize)
yarn jest meadowlark/integration-tests/basic-navigation.test.js to only run your file

Express + Mocha + Chai Error: Server is not listening

I'm writing a simple TypeScript Express app that would get info about YouTube videos. Here is the router (mounted to /api):
import express from 'express';
import ytdl from 'ytdl-core';
import bodyParser from 'body-parser';
const router = express.Router();
router.post('/info', bodyParser.json(), async (req, res, next) => {
const videoID = req.body.videoID
if (!ytdl.validateID(videoID)) {
const error = new Error('Invalid video ID')
res.status(400).json({error: error.toString()}).send;
next();
} else {
const videoFormats = await (await ytdl.getInfo(videoID)).formats;
res.setHeader('Content-type', 'application/json');
res.send(videoFormats);
}
});
export default router;
Today I've attempted to write my first tests with Mocha + Chai. The api/info endpoint expects POST, grabs videoID from the body and responds with JSON. Here are my tests for the endpoint so far.
import app from '../index'
import chaiHttp from 'chai-http'
import chai from 'chai'
chai.use(chaiHttp);
const expect = chai.expect;
const get = chai.request(app).get;
const post = chai.request(app).post;
describe('API', () => {
describe('POST /api/info', () => {
it('given videoID of a publically available video, responds with 200 OK and JSON that contains an array of objects', async () => {
const res = await post('/api/info')
.send({"videoID": "OrxmtDw4pVI"});
expect(res).to.have.status(200);
expect(res.body).to.be.an('array');
}).timeout(5000);
it('given an invalid videoID, responds with 400 and an Error', async () => {
const res = await post('/api/info')
.send({"videoID": "qwerty"});
expect(res).to.have.status(400);
});
});
});
The test results are as follows:
API
POST /api/info
√ given videoID of a publically available video, responds with 200 OK and JSON that contains an array of objects (1028ms)
1) given an invalid videoID, responds with 400 and an Error
1 passing (1s)
1 failing
1) API
POST /api/info
given an invalid videoID, responds with 400 and an Error:
Error: Server is not listening
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! ytb--down#1.0.0 test: `mocha -r ts-node/register src/**/*.spec.ts --exit`
npm ERR! Exit status 1
As you can see, the second test fails due to the Server is not listening error. It doesn't happen if I skip the first test.
I used Postman to test this endpoint with this data manually and found no issues that would cause server crashes, no errors are thrown.
I launch tests with the npm run test command, here is the script from package.json:
"scripts": {
"test": "mocha -r ts-node/register src/**/*.spec.ts --exit"
I suspect there is something wrong with the test suite itself, but I am unable to pinpoint a solution. What am I missing here?
I think I found it.
Seems like my tests did contain a mistake and not an obvious one for a beginner like myself.
You don't want to get get and post shortcuts like so:
const get = chai.request(app).get;
const post = chai.request(app).post;
I think the actual mistake is that I invoked request() here. Instead, should've just grabbed a shortcut for chai.request:
const request = chai.request;
Tests work fine now. Test file now looks as follows.
import server from '../index'
import chaiHttp from 'chai-http'
import chai from 'chai'
chai.use(chaiHttp);
const expect = chai.expect;
const request = chai.request;
describe('API', () => {
describe('POST /api/info', () => {
it('given videoID of a publically available video, responds with 200 and JSON that contains an array of objects', async () => {
const res = await request(server)
.post('/api/info')
.send({"videoID": "OrxmtDw4pVI"});
expect(res).to.have.status(200);
expect(res.body).to.be.an('array');
});
it('given an invalid videoID, responds with 400 and an Error', async () => {
const res = await request(server)
.post('/api/info')
.send({"videoID": "qwerty"});
expect(res).to.have.status(400);
expect(res.body.error).to.include('Error');
});
});
});

Getting error Oauth error invalid_request: The redirect_uri is not whitelisted from Shopify API access through local host?

I have looked at the other shopify questions here but this time the error seems to come from something else I believe. I am trying to create/access a shopify app through localhost on nodejs for playing around with the Product API in the future. But encountering the above error:
Here is my NGROK log:
GET /shopify 302 Found GET /shopify 302 Found GET /shopify 302 Found GET /shopify 302 Found GET /shopify 302 Found GET /shopify 302 Found
1- Here is my index.js file:
///////////// Initial Setup /////////////
const dotenv = require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const cookie = require('cookie');
const nonce = require('nonce')();
const querystring = require('querystring');
const axios = require('axios');
const shopifyApiPublicKey = process.env.SHOPIFY_API_PUBLIC_KEY;
const shopifyApiSecretKey = process.env.SHOPIFY_API_SECRET_KEY;
const scopes = 'write_products';
const appUrl = 'https://20a11edc124f.ngrok.io/';
const app = express();
const PORT = 3000
app.get('/', (req, res) => {
res.send('Ello Govna')
});
///////////// Helper Functions /////////////
const buildRedirectUri = () => `${appUrl}/shopify/callback`;
const buildInstallUrl = (shop, state, redirectUri) => `https://${shop}/admin/oauth/authorize?client_id=${shopifyApiPublicKey}&scope=${scopes}&state=${state}&redirect_uri=${redirectUri}`;
const buildAccessTokenRequestUrl = (shop) => `https://${shop}/admin/oauth/access_token`;
const buildShopDataRequestUrl = (shop) => `https://${shop}/admin/shop.json`;
const generateEncryptedHash = (params) => crypto.createHmac('sha256', shopifyApiSecretKey).update(params).digest('hex');
const fetchAccessToken = async (shop, data) => await axios(buildAccessTokenRequestUrl(shop), {
method: 'POST',
data
});
const fetchShopData = async (shop, accessToken) => await axios(buildShopDataRequestUrl(shop), {
method: 'GET',
headers: {
'X-Shopify-Access-Token': accessToken
}
});
///////////// Route Handlers /////////////
app.get('/shopify', (req, res) => {
const shop = req.query.shop;
if (!shop) { return res.status(400).send('no shop')}
const state = nonce();
const installShopUrl = buildInstallUrl(shop, state, buildRedirectUri())
res.cookie('state', state) // should be encrypted in production
res.redirect(installShopUrl);
});
app.get('/shopify/callback', async (req, res) => {
const { shop, code, state } = req.query;
const stateCookie = cookie.parse(req.headers.cookie).state;
if (state !== stateCookie) { return res.status(403).send('Cannot be verified')}
const { hmac, ...params } = req.query
const queryParams = querystring.stringify(params)
const hash = generateEncryptedHash(queryParams)
if (hash !== hmac) { return res.status(400).send('HMAC validation failed')}
try {
const data = {
client_id: shopifyApiPublicKey,
client_secret: shopifyApiSecretKey,
code
};
const tokenResponse = await fetchAccessToken(shop, data)
const { access_token } = tokenResponse.data
const shopData = await fetchShopData(shop, access_token)
res.send(shopData.data.shop)
} catch(err) {
console.log(err)
res.status(500).send('something went wrong')
}
});
///////////// Start the Server /////////////
app.listen(PORT, () => console.log(`listening on port ${PORT}`));
2- Here is my env file
SHOPIFY_API_PUBLIC_KEY=key here
SHOPIFY_API_SECRET_KEY=key here
3- Here is my package.json file
{
"name": "TestQasim",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.20.0",
"cookie": "^0.4.1",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"nodemon": "^2.0.4",
"nonce": "^1.0.4"
}
}
Im using NGROK tunneling and using this link:
https://20a11edc124f.ngrok.io/shopify?shop=testqasim121312.myshopify.com
but getting the above mentioned error.
I have checked the API URLS etc and cannot seem to find any issue in those either.
this is a screenshot of the error
Incase you're still facing this issue,
Remove the trailing slash from the appURL, I think that should fix this issue.
Use const appUrl = 'https://20a11edc124f.ngrok.io'
not const appUrl = 'https://20a11edc124f.ngrok.io/'
and don't forget to restart your node server.
Log into your partner account, and for the App with the API keys, whitelist the url. It really is that simple.
check you ngrok url with you app setup url and Allowed redirection URL(s) both solub be same like https or http check properly
For someone who has the same problem later, I leave a message.
I just solved it.
In your package.json, you might have this command. => "dev": "node server.js"
It never restart your server when code is edited.
So, after chaing ngrok address(ex, https://12k1ml2kd1m.ngrok.io), restart your server.
In short, change your ngrok address and restart your node server.
In my case, Shopify automatically generates the .env file and automatically adds the redirect URL whitelist, but the problem is that Shopify auto-populates the wrong values.
The autofill value is
https://36f6-45-251-107-111.ngrok.io/auth/callback
The correct value is: https://36f6-45-251-107-111.ngrok.io/shopify/auth/callback
So please check if the redirect URL is consistent with the whitelist after decoding the URL in the error page.

Problems running mocha on Firebase functions

I wrote a Node/Typescript app that used data from Firebase Cloud Firestore. The app worked perfectly and I was able to test my endpoints fine using simple mocha commands of the generated .js file. Here is an example of one of the test scripts:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', () => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200)
)
it('does not allow put at top level', () =>
supertest(app)
.put('/risks')
.expect(403)
)
})
Here is the App.js that is referring to (imports and declarations excluded):
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
Here is the Risk router with only the GET endpoint:
const express = require('express');
const bodyParser = require('body-parser');
const riskRouter = express.Router();
import { firestore, firebasestore } from '../firebase/firebase';
riskRouter.use(bodyParser.json());
riskRouter.route('/')
.get((req,res,next) => {
return firestore.collection('risks').get()
.then(snapshot => {
let risks = [];
snapshot.forEach(doc => {
const data = doc.data()
const _id = doc.id
risks.push({_id, ...data });
});
res.send(risks)
})
.catch( err => res.json({error: err}))
})
// POST, PUT and DELETE are implemented here but not needed for this discussion
module.exports = riskRouter
When I tried to migrate this to Firebase, I basically copied the entire node application to the /functions directory and made the following change to the App.ts file
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
In both cases, the test command in package.json is
"test": "tsc && mocha lib/**/*.spec.js"
Also, the Risk router is identical in both cases.
In the case that works, all the test simply run cleanly. Also, they are making calls to the external Firebase backend
In the case that fails, I get the following output:
Risk API
1) works to get /
0 passing (2s)
1 failing
1) Risk API works to get /:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
All of my endpoints use some form of res.send(), res.json(), etc which I read is sufficient and the explicit use of done() is not needed. If I'm wrong about that, I'd like to know as well as the proper syntax to fix it.
I also tried running mocha directly on the generated test script using the --timeout 15000 option, but got the same result.
Any suggestions would be greatly appreciated!
Can you try the following:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', (done) => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200, done)
)
it('does not allow put at top level', (done) =>
supertest(app)
.put('/risks')
.expect(403, done)
)
})

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

Resources