jest/ts/ tests Cannot find name 'x' - node.js

I've read a ton of the previous SO questions regarding this however, none seems to solve any of my problems.
index.test.ts
import request from 'supertest';
import * as server from '../server';
import 'jest'
//close server after each request
afterEach( //cannot find name 'afterEach'
async (): Promise<void> => {
await server.close(); // Property 'close' does not exist on type 'typeof import(<path to file)'
},
);
describe('get /', (): void => { //Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i #types/jest` or `npm i #types/mocha`
it('should respond as expected', async (): Promise<void> => {// cannot find 'it'...
const response = await request(server).get('/');
expect(response.status).toEqual(200); // cannot find 'expect'
expect(response.body.data).toEqual('Sending some JSON'); // cannot find 'expect'...
});
});
I have installed both jest and mocha types, just to make sure that it wasn't registering one over the other. Pretty much everything is not being recognized,
.babelrcc
{
"presets": ["#babel/preset-typescript"]
}
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"preserveConstEnums": true,
"outDir": "./dist",
"sourceMap": true,
"esModuleInterop": true
},
"include": ["./src/**/*"],
"exclude": ["test", "**/*/spec.ts", "**/*/test.ts"]
}
server
import * as dotenv from 'dotenv';
dotenv.config();
import Koa from 'koa';
import logger from 'koa-logger';
import apiRoutes from './Routes';
import cors from '#koa/cors';
import bodyParser from 'koa-bodyparser';
const app = new Koa();
const PORT = process.env.PORT || 3001;
const ENV = process.env.NODE_ENV || 'Development';
app.use(logger());
app.use(cors());
app.use(bodyParser());
app.use(apiRoutes);
export const server = app.listen(PORT, (): void => {
console.log(`🌍 Server listening on port ${PORT} - ${ENV} environment`);
});
I don't know what other information is relevant if there is something else I am missing I will update this with the information

You are exporting the named variable server directly while you are trying to import it as if it were the default export.
Just change it to import { server } from './server'; in index.test.ts and it will work as expected. You can also change the export to export default server in server.ts but I tend to avoid that based on this article.
Read more about export on MDN.

Related

Jest doesn't see the default export with ESM configured

I'm using Jest with ts-jest module. I've followed the instructions on how to enalbe ESM, seems like ESM is working now. But when running tests, Jest doesn't see default export of my app, error:
SyntaxError: The requested module '../app' does not provide an export named 'default'
I've commented all tests, except the first one, now my test file looks like this:
describe("User registration", () => {
it("GET should return 404", async () => {
const res = await request(app).get("/api/register");
assert.equal(res.statusCode, 404);
});
}
app.ts:
import dotenv from "dotenv";
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import fileUpload from "express-fileupload";
import router from "./router/index";
import errorMiddleware from "./middlewares/error-middleware";
dotenv.config();
const app = express();
// enable req.ip
app.set("trust proxy", true);
app.use(fileUpload());
app.use(express.json({ limit: "1000mb" }));
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: process.env.CLIENT_URL,
})
);
app.use("/api", router);
app.use(errorMiddleware);
export default app;
The only thing i tried was replacing
import app from "../app"; with import { default as app } from "../app";
Path to app.ts is correct
My tsconfig.json:
{
"compilerOptions": {
/* Language and Environment */
"target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
/* Modules */
"module": "ES6" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
"baseUrl": "./src" /* Specify the base directory to resolve non-relative module names. */,
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
"outDir": "./build" /* Specify an output folder for all emitted files. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
Jest version: ^29.3.1
TypeScript version: 4.9.4
mate. Got rid of this error here. Looks like you just forgot to export the variable app... so, if everything is configured properly, adding this line to your app.ts should do the trick:
import dotenv from "dotenv";
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import fileUpload from "express-fileupload";
import router from "./router/index";
import errorMiddleware from "./middlewares/error-middleware";
dotenv.config();
const app = express();
// enable req.ip
app.set("trust proxy", true);
app.use(fileUpload());
app.use(express.json({ limit: "1000mb" }));
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: process.env.CLIENT_URL,
})
);
app.use("/api", router);
app.use(errorMiddleware);
export default app; // <--- THIS LINE HERE

Expressjs and TypeScript - unable to type express

I am trying to properly type nodejs application with use of express and TypeScript. Consider this code:
import express, { Router } from "express";
import { DeviceController } from "../controller/device.controller";
export class DeviceRouter {
public router: Router;
private deviceController: DeviceController;
constructor() {
this.deviceController = new DeviceController();
this.router = express.Router();
this.router.get("/", this.deviceController.index);
this.router.post("/", this.deviceController.create);
this.router.delete("/:id", this.deviceController.delete);
}
}
This leads to error
TSError: ⨯ Unable to compile TypeScript:
src/module/device/router/device.router.ts:13:17 - error TS2339: Property 'get' does not exist on type 'Router'.
13 this.router.get("/", this.deviceController.index);
~~~
src/module/device/router/device.router.ts:14:17 - error TS2339: Property 'post' does not exist on type 'Router'.
14 this.router.post("/", this.deviceController.create);
~~~~
src/module/device/router/device.router.ts:15:17 - error TS2339: Property 'delete' does not exist on type 'Router'.
15 this.router.delete("/:id", this.deviceController.delete);
~~~~~~
Of course when router is typed as any everything is working as intended. I have the same problem with typing app, firstly code example when everything is working good:
import express from "express";
class App {
public app: any;
constructor() {
this.app = express();
}
}
export default new App().app;
and example when typing leads to error:
import express, { Application } from "express";
class App {
public app: Application;
constructor() {
this.app = express();
}
}
export default new App().app;
result:
src/server.ts:5:5 - error TS2339: Property 'listen' does not exist on type 'Application'.
5 app.listen(PORT, () => console.log(`App listening on port ${PORT}!`));
My configuration:
macOS Catalina 10.15.2
node.js v10.16.0
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["src/**/*"]
}
relevant dependencies:
"dependencies": {
"express": "~4.17.1"
},
"devDependencies": {
"#types/express": "~4.17.2",
"#types/node": "~13.5.0",
"ts-node": "~8.6.2",
"typescript": "~3.7.5"
}

How can I render a Lit Element Web component using Typescript Express App?

I have created a Typescript Express Server:
src/server.ts
import express from "express";
import { HomeController } from "./controllers";
const app: express.Application = express();
const port: number = ((process.env.PORT as any) as number) || 3000;
app.use(express.static("static"));
app.use("/", HomeController);
app.listen(port, () => {
// tslint:disable-next-line:no-console
console.log(`Listening at http://localhost:${port}/`);
});
src/controllers/index.ts
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", (req: Request, res: Response) => {
res.send("Hello World");
});
export const HomeController: Router = router;
structure
├───build
│ ├───common
│ ├───components
│ └───controllers
├───src
│ ├───common
│ ├───components
│ └───controllers
└───static
└───images
I have tried hosting a static file. Ex. index.html. via res.send('index.html'); The file is rendered but I am unable to import the element using a script tag. As the error returned is Exports is not defined
src/components/card.ts
import { html, LitElement } from "lit-element";
class Card extends LitElement {
protected render() {
return html`
<img src="../../static/images/AS.png" />
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"card-element": Card;
}
}
customElements.define("card-element", Card);
I am using TSC to build my application. I manually copied my static folder into by build folder to use. Im not sure if there is an automatic way to copy this folder on build.
Is there something that I am doing wrong with my compiler that may be giving me the error Exports is not defined research says its something to do with CommonJs but I tried installing and the result didnt change
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6", "dom"],
"outDir": "./build",
"strict": true,
"moduleResolution": "node",
"typeRoots": ["./modules"],
"esModuleInterop": true,
"skipLibCheck": true
}
}
I resolved my problem by bundling my code using webpack. This allowed me to create an Index.html importing my bundle.js file

Re-direct dynamic URLs from express to react

I have a react app, which uses a router and works fine if the URL is not entered directly into the address bar, but rather is set by react router when navigating the app. I have added ExpressJS to the app, by running the react app via the Express proxy. The goal is to re-direct any dynamic requests to the react app, so that it can handle it, instead of Express. I have looked at many examples and tried using them, but I had no luck in making the re-direct work.
The folder structure and key file locations are as follows:
[Root directory]
---- [client]
--------- [node_modules]
--------- [public]
--------- [scss]
--------- [src]
--------- package.json
--------- webpack.config.js
---- [server]
--------- index.js
---- [node_modules]
---- package.json
The "client" directory contains the react app. The "server" directory contains the Express app. The Express app's package.json is in the root directory, while react app's package.json is in the "client" directory.
server/index.js
const express = require('express');
const path = require('path');
const PORT = process.env.PORT || 5000;
const app = express();
// Serve static files
app.use(express.static(path.resolve(__dirname, '../client/public')));
app.get('*', function(request, response) {
response.sendFile(path.resolve(__dirname, '../client/public', 'index.html'));
});
app.listen(PORT, function () {
console.error(`Listening on port ${PORT}`);
});
client/package.json
Contains "proxy": "http://localhost:5000"
client/webpack.config.js
Contains the following for devServer, entry, and output (I excluded various loaders as irrelevant for URL processing):
const BUILD_DIR = path.resolve(__dirname, 'build');
const SRC_DIR = path.resolve(__dirname, 'src');
module.exports = (env = {}) => {
return {
entry: {
index: [SRC_DIR + '/index.js']
},
output: {
path: BUILD_DIR,
filename: '[name].bundle.js'
},
devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
devServer: {
contentBase: BUILD_DIR,
compress: true,
hot: true,
open: true,
historyApiFallback: true
}
}
};
client/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import createHistory from 'history/createBrowserHistory';
import { Route } from 'react-router';
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';
import rootReducer from './reducers';
// Wrapper
import SiteWrapper from './wrappers/SiteWrapper/'
// Pages
import Error404 from './ErrorPages/Error404/'
const history = createHistory();
const middleware = routerMiddleware(history);
const store = createStore(
combineReducers({
rootReducer,
router: routerReducer
}),
applyMiddleware(middleware)
)
ReactDOM.render((
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Route exact path="/404" name="Error 404" component={Error404}/>
<Route path="/" name="Home" component={SiteWrapper}/>
</div>
</ConnectedRouter>
</Provider>
), document.getElementById('root'));
I first run the Express app, which starts listening on port 5000, and then I run the react app. When entering a dynamic URL http://localhost:7500/dynamicurl/test-dynamic-url, the app no longer shows 404, but then it doesn't show anything at all, and this is what I see in the developer console:
Refused to apply style from 'http://localhost:7500/dynamicurl/index.fonts.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
test-dynamic-url:1 Refused to apply style from 'http://localhost:7500/dynamicurl/index.styles.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
test-dynamic-url:54 GET http://localhost:7500/dynamicurl/index.bundle.js net::ERR_ABORTED
What am I doing wrong here? Did I miss something?
Thanks in advance

TypeScript Express Api class is not a constructor when testing with ts-node

I have set up an express application using typescript classes and have run into a strange issue. All tests have been passing, and today when I went to update some of the routes, my tests no longer run. When I run my test script, I get this error message back in the console:
$ mocha -c --reporter spec --compilers ts:ts-node/register ./test/*.test.ts --
timeout 20000
/Users/christiantodd/Development/projects/bby-react-api/src/index.ts:8
const app: Api = new Api();
^
TypeError: Api_1.default is not a constructor
at Object.<anonymous> (/Users/christiantodd/Development/projects/bby-react-api/src/index.ts:8:18)
at Module._compile (module.js:635:30)
at Module.m._compile (/Users/christiantodd/Development/projects/bby-react-api/node_modules/ts-node/src/index.ts:392:23)
at Module._extensions..js (module.js:646:10)
at Object.require.extensions.(anonymous function) [as .ts] (/Users/christiantodd/Development/projects/bby-react-api/node_modules/ts-node/src/index.ts:395:12)
My Api.ts file looks as follows:
import * as bodyParser from 'body-parser';
import * as express from 'express';
import * as expressValidator from 'express-validator';
import * as helmet from 'helmet';
import * as morgan from 'morgan';
import * as passport from 'passport';
import * as compression from 'compression';
/* import all routers */
import BestBuyRouter from './routes/BestBuyRouter';
import UserRouter from './routes/UserRouter';
export default class Api {
/* reference to the express instance */
public express: express.Application;
/* create the express instance and attach app level middleware and routes */
constructor() {
this.express = express();
this.middleware();
this.routes();
}
/* get current environment */
public currentEnv(): string {
return this.express.get('env');
}
/* apply middleware */
private middleware(): void {
this.express.use((req, res, next) => {
/* Don't allow caching. Needed for IE support :/ */
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
res.header('Pragma', 'no-cache');
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Methods',
'PUT, GET, POST, DELETE, OPTIONS'
);
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials'
);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
this.express.use(compression());
this.express.use(helmet());
this.express.use(morgan('dev'));
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: false }));
this.express.use(passport.initialize());
this.express.use(expressValidator());
this.express.use((err, req, res, next) => {
console.error(err);
res.status(err.status || 500).json({
message: err.message,
error: err
});
});
}
/* connect resource routers */
private routes(): void {
/* create an instance of the each of our routers */
const userRouter = new UserRouter();
const bestBuyRouter = new BestBuyRouter();
/* attach all routers to our express app */
this.express.use(userRouter.path, userRouter.router);
this.express.use(bestBuyRouter.path, bestBuyRouter.router);
}
}
and my index.ts:
import Api from './Api';
require('dotenv').config();
const mongoose = require('mongoose');
/* Set mongoose promise to native ES6 promise */
mongoose.Promise = global.Promise;
/* Instantiate our app instance */
const app: Api = new Api();
const connectOptions = {
useMongoClient: true,
keepAlive: true,
reconnectTries: Number.MAX_VALUE
};
/* Get current environment */
export const ENV = app.currentEnv();
let DATABASE_URL;
let PORT;
/* set environment variables */
if (ENV === 'production') {
DATABASE_URL = process.env.MONGODB_URI;
PORT = parseInt(process.env.PORT, 10);
} else {
DATABASE_URL = process.env.TEST_DATABASE_URL;
PORT = 3000;
}
let server;
export const runServer = async (
dbURL: string = DATABASE_URL,
port: number = PORT
) => {
try {
await mongoose.connect(dbURL, connectOptions);
await new Promise((resolve, reject) => {
server = app.express
.listen(port, () => {
console.info(`The ${ENV} server is listening on port ${port} 🤔`);
resolve();
})
.on('error', err => {
mongoose.disconnect();
reject(err);
});
});
} catch (err) {
console.error(err);
}
};
export const closeServer = async () => {
try {
await mongoose.disconnect();
await new Promise((resolve, reject) => {
console.info(`Closing server. Goodbye old friend.`);
server.close(err => (err ? reject(err) : resolve()));
});
} catch (err) {
console.error(err);
}
};
require.main === module && runServer().catch(err => console.error(err));
Lastly, my tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "es7"],
"allowJs": true,
"watch": true,
"noImplicitAny": false,
"removeComments": true,
"sourceMap": false,
"target": "es6",
"module": "commonjs",
"outDir": "./lib",
"types": [
"body-parser",
"mongodb",
"mongoose",
"passport",
"node",
"nodemailer",
"mocha",
"chai",
"express",
"express-validator",
"chai-http"
],
"typeRoots": ["./node_modules/#types"]
},
"compileOnSave": true,
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "**/*.test.ts"]
}
To me it's really strange to get this behavior all of the sudden when this config has worked for me just fine in the past. I can still start my server just fine, but for some reason ts-node doesn't want to compile my *test.ts files for mocha to run my tests. Any idea what this could be?
Debugging "is not a ..." errors
So this is likely NOT going to be the answer but this question was the top result for the error I was debugging and here is a debugging tip.
I was running mocha with the following:
test/mocha.opts
--require ts-node/register
--require source-map-support/register
--watch-extensions ts
But no matter how I imported ./app I could not get classes or functions to work despite tsc compiling fine and node and mocha working fine on the compiled .js files.
import * as app from './app'
console.log({app}); // Pretty print object
As is would happen I had a Heroku project with app.json and app.ts.
The combination of mocha and ts-node were loading the .json file extension as a higher priority instead of the .ts file and Typescript won't allow me to specify a file extension. So this behaviour is different in tsc vs mocha + ts-node.
Bonus Points - Typescript Code Coverage
Unit tests: nyc mocha src/**/*-test.ts
Integration tests: nyc mocha test/**/*.ts
package.json
{
"nyc": {
"extension": [
".ts"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*-test.ts",
"test/**/*.ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"text-summary"
],
"sourceMap": true,
"instrument": true,
"all": true
}
}
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target":"es2017",
"esModuleInterop":true,
// "strict": true,
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"types":["node"],
"rootDirs":[
"src"
]
},
"include": [
"src/**/*",
"test/**/*"
],
"exclude":[
"**/node_modules/**"
]
}

Resources