使用 ts-node 进行测试时,TypeScript Express Api 类不是构造函数

Posted

技术标签:

【中文标题】使用 ts-node 进行测试时,TypeScript Express Api 类不是构造函数【英文标题】:TypeScript Express Api class is not a constructor when testing with ts-node 【发布时间】:2018-04-20 12:56:36 【问题描述】:

我已经使用 typescript 类设置了一个 express 应用程序,但遇到了一个奇怪的问题。所有测试都通过了,今天我去更新一些路线时,我的测试不再运行。当我运行我的测试脚本时,我在控制台中收到了这条错误消息:

$ 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)

我的Api.ts 文件如下所示:

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);
  

还有我的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));

最后,我的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"]

对我来说,当这个配置过去对我很有效时,突然出现这种行为真的很奇怪。我仍然可以很好地启动我的服务器,但出于某种原因,ts-node 不想编译我的 *test.ts 文件以供 mocha 运行我的测试。知道这可能是什么吗?

【问题讨论】:

你如何运行你的单元测试,script's如果你提供一个 package.json 文件将有助于找出问题 你最终得到答案了吗?我认为我在使用 ts-node 模块加载器并尝试将快速应用程序解析为函数承诺的结果时遇到了与 Mocha 类似的问题。 【参考方案1】:

调试“is not a ...”错误

所以这可能不是答案,但这个问题是我正在调试的错误的最佳结果,这里有一个调试提示。

我正在运行 mocha 并执行以下操作:

test/mocha.opts

--require ts-node/register
--require source-map-support/register
--watch-extensions ts

但无论我如何导入 ./app,尽管 tsc 编译正常,nodemocha 在编译后的 .js 文件上工作正常,但我无法让类或函数工作。

import * as app from './app'
console.log(app); // Pretty print object

我有一个带有app.jsonapp.ts 的Heroku 项目。

mochats-node 的组合将 .json 文件扩展名作为更高的优先级而不是 .ts 文件加载,Typescript 不允许我指定文件扩展名。所以这种行为在tscmocha + ts-node 中是不同的。

加分 - 打字稿代码覆盖率

单元测试:nyc mocha src/**/*-test.ts

集成测试: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/**"
  ]


【讨论】:

以上是关于使用 ts-node 进行测试时,TypeScript Express Api 类不是构造函数的主要内容,如果未能解决你的问题,请参考以下文章

使用ts-node使用vscode调试Alsatian测试用例

找不到模块 'ts-node/register'

在 mocha 测试中使用带有 ts-node 的断言

使用 mocha 和 ts-node 运行位于单独目录中的测试?

使用ts-node执行时使用打字稿导入nodejs`fs`?

使用 ts-node 时通过 fork() 调用子进程