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
Related
I am trying to run my express server in node that I have used in the past and I am getting the follow error in VSCode:
node:internal/modules/cjs/loader:1063
throw err;
^
Error: Cannot find module '/Users/valemich/Desktop/code/src/vscode/calls_sales/start'
at Module._resolveFilename (node:internal/modules/cjs/loader:1060:15)
at Module._load (node:internal/modules/cjs/loader:905:27)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
at node:internal/main/run_main_module:23:47 {
code: 'MODULE_NOT_FOUND',
requireStack: []
I am on a MACOS Ventura using Node version v.19.5.0 and here is my package.json. Should I uninstall node completely and reinstall? Do I have node installed in the wrong folder? I am completely at a loss for what the issue is.
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.2.5",
"express": "^4.18.2"
},
"devDependencies": {
"ngrok": "^4.3.3"
}
and my server file is as follows:
/* eslint-disable object-shorthand */
/* eslint-disable comma-dangle */
/* eslint-disable semi */
require('dotenv').config();
const express = require('express');
const axios = require('axios').default;
// const Airtable = require('airtable');
// const base = new Airtable({ apiKey: `${process.env.AIRTABLE_TOKEN}` }).base(
// `${process.env.AIRTABLE_CRM_ID}`
// );
const app = express();
const port = 3000;
app.use(express.json());
app.get('/', (req, res) =>
res.send(`
<html>
<head><title>Success!</title></head>
<body>
<h1>You did it!</h1>
<img src="https://media.giphy.com/media/XreQmk7ETCak0/giphy.gif" alt="Cool kid doing thumbs up" />
</body>
</html>
`)
);
app.post(`${process.env.AIRTABLE1_WEBHOOKS_URL}`, (req, res) => {
// TODO: Update the base to show call date, call time, and questions
// TODO: Create API to Calendly to find out who is assigned the call and the event name. Then post back again with information
// TODO: duplicate this information to post to Slack as well (depending on event name to which channel it posts to.)
const eventUrl = req.body.payload.event;
const event = eventUrl.split('/')[4];
console.log(event);
const questions = req.body.payload.questions_and_answers;
const question = [];
// eslint-disable-next-line no-undef
for (i = 0; i < questions.length; i++) {
// eslint-disable-next-line no-undef
question.push(questions[i].question, questions[i].answer);
console.log(question);
}
// const content = `:wave: ${username} just starred ${repoName} :taco: :rocket:`;
// const avatarUrl = req.body.sender.avatar_url;
axios
.post(process.env.AIRTABLE2_WEBHOOKS_URL, {
content: content,
embeds: [
{
image: {
url: avatarUrl,
},
},
],
})
.then((airTableResponse) => {
console.log('Success!');
res.status(204).send();
})
.catch((err) => console.error(`Error sending to AirTable: ${err}`));
});
app.use((error, req, res, next) => {
res.status(500);
res.send({ error: error });
console.error(error.stack);
next(error);
});
app.listen(port, () =>
console.log(`Example app listening at http://localhost:${port}`)
);
I was trying to use gzip to reduce the size of the bundle of my current angular project. The bundle report claims that after using gzip it would reduce my product-build's size to about 30% or less. But I don't know how to make that work.
Currently, my project is built in Angular and is sent to a port through Node.js Express.js server. Guess we would need config both Angular and NodeJS to make it work?
What I have tried so far:
It looks like the gzip can be applied in Angular 5- until Google disabled the ng eject. Also, I found a lot of Nginx tutorials with gzip but none of Node.js by now. So guess I don't quite understand either of them.
I tried to add the following code in my node server, but it doesn't seem to affect the performance of the project with or without the compression.
const express = require('express');
const http = require('http');
const https = require('https');
const compression = require('compression');
const app = express();
const port = 4201;
app.use(compression());
app.use(express.static(__dirname + '/dist'));
Edit:
After testing with http://www.gidnetwork.com/tools/gzip-test.php, my site has been compressed into gzip. But the performance in Google Page Insight doesn't change. Perhaps only the index.html is compressed?
I use node version 16.13.2 and it has a built-in compression package.
So, all you need is to use the below server.ts file as your node server
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
const compression = require('compression');
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/your-project-name/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
server.use(compression());
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
Try zlib, nodejs has a stable support in core now.
An example (taken from the docs):
// server example
// Running a gzip operation on every request is quite expensive.
// It would be much more efficient to cache the compressed buffer.
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
var raw = fs.createReadStream('index.html');
var acceptEncoding = request.headers['accept-encoding'];
if (!acceptEncoding) {
acceptEncoding = '';
}
// Note: this is not a conformant accept-encoding parser.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
if (acceptEncoding.match(/\bdeflate\b/)) {
response.writeHead(200, { 'content-encoding': 'deflate' });
raw.pipe(zlib.createDeflate()).pipe(response);
} else if (acceptEncoding.match(/\bgzip\b/)) {
response.writeHead(200, { 'content-encoding': 'gzip' });
raw.pipe(zlib.createGzip()).pipe(response);
} else {
response.writeHead(200, {});
raw.pipe(response);
}
}).listen(1337);
for angular you have to install gzip-all
npm i gzip-all
and on your package.json file
package.json
"scripts": {
"build": "ng build --prod --no-delete-output-path --aot --base-href /app/ && gzip-all \Project file.....\" ",
},
I'm able to dynamically render html for server side rendering using angular 8 and angular universal when I view page source BUT my issue is that I am rendering the dynamic html before the api request has finished loading so I can't see the response in the dynamic html from the view page source - please let me know if you require any further info. I run this command:
i.e. npm run build:ssr && npm run serve:ssr
and when I look at the logs and change the url I get a [NetworkError] in the console but the app still runs as expected, I need to figure out a way to load the api response and then render the html into the page view source after the request has finished but I've run out of ideas.
Hopefully one of you guys can help,
Thanks
Server.ts
import 'zone.js/dist/zone-node';
import 'localstorage-polyfill';
import { join } from 'path';
import * as express from 'express';
const compression = require('compression');
const sessionStorage = require('sessionstorage');
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const fs = require('fs');
const template = fs.readFileSync('./dist/browser/index.html').toString();
const win = domino.createWindow(template);
const proxy = require('http-proxy-middleware');
const cors = require('cors');
const helmet = require('helmet');
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
global['window'] = win;
global['Node'] = win.Node;
global['navigator'] = win.navigator;
global['Event'] = win.Event;
global['KeyboardEvent'] = win.Event;
global['MouseEvent'] = win.Event;
global['Event']['prototype'] = win.Event.prototype;
global['document'] = win.document;
global['sessionStorage'] = sessionStorage;
global['localStorage'] = localStorage;
// Express server
const app = express();
const PORT = process.env.PORT || 4200;
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
app.use(cors());
app.use(compression());
// express-engine
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// Protect website from Clickjacking attack
app.use(helmet.frameguard());
app.use(helmet.xssFilter());
// Proxy API Endpoints
app.use('/api/profileProxy', proxy(
{
target: 'http://xxxxxxx1:9004', // target host
changeOrigin: true, // needed for virtual hosted sites
// ws: true, // proxy websockets
pathRewrite: {
'^/api/profileProxy': ''
}
}
));
app.use('/api/searchProxy', proxy(
{
target: 'http://xxxxxx.160:9005', // target host
changeOrigin: true, // needed for virtual hosted sites
// ws: true, // proxy websockets
pathRewrite: {
'^/api/searchProxy': ''
}
}
));
app.get('/sitemap1.xml', function (req, res, next) {
const file = `${DIST_FOLDER}/sitemap1.xml`;
fs.exists(file, function (exists) {
if (exists) {
res.sendFile(file);
} else {
res.status(404).send('404');
}
});
});
app.get('/robots.txt', function (req, res, next) {
const file = `${DIST_FOLDER}/robots.txt`;
fs.exists(file, function (exists) {
if (exists) {
res.sendFile(file);
} else {
res.status(404).send('404');
}
});
});
// All regular routes use the Universal engine
app.get('*', (req, res) => {
console.time(`GET: ${req.originalUrl}`);
console.log(`req-QQQQQQQQQQ: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
console.timeEnd(`GET: ${req.originalUrl}`);
console.log(`req-timeEnd: ${req.originalUrl}`);
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
``
What you are describing is normal behaviour. Only your original request (e.h. when you type in the site's adress) in the URL bar will be prerendered using angular universal. This prerendered html is what you can see if you use View Source (or Ctrl +U)
Once the page is displayed, the client side angular app takes over. When you click on the search button, it'll perform an API call and get results, which will update the page. You'll see that html using the Html inspector from your browser's debugging tool, but it will have no impact on the original HTML that has been sent by the server
for people that may come across this issue - I was able to display the data from the api request (in my case the initial search results) in the view page source for SSR by making a GET request in the app.component of my app - thanks
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.
I am using angular 6 and implemented the angular universal for server side page rendering,
Everything works fine but I am getting continues error in my console.
ERROR [Error]
ERROR [Error]
And I have declared window as global variable still I am getting errors like this
Unhandled Promise rejection: window.some_function is not a
function
my server.ts file
//
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// node run port
const port = 6065;
// Express server
const app = express();
const PORT = port || process.env.PORT;
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const fs = require('fs');
const MockBrowser = require('mock-browser').mocks.MockBrowser;
const mock = new MockBrowser();
const template = fs
.readFileSync(join(DIST_FOLDER, 'browser', 'index.html'))
.toString();
// Make all Domino types available as types in the global env.
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
const win = domino.createWindow(template);
win.Object = Object;
win.Math = Math;
global['window'] = win;
global['document'] = win.document;
global['navigator'] = mock.getNavigator();
global['branch'] = null;
global['object'] = win.object;
global['HTMLElement'] = win.HTMLElement;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, '0.0.0.0', () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
I had the same problem and started debugging server.js in my Visual Studio Code and what I had found: an error messages was caused by http requests from TranslateHttpLoader (I'm using ngx-translate), which was trying to load .json-files with translations. After some googling I found this thread where you can find how to enable server-side translation loading. After that error messages disappeared.