确保 Express 应用程序在每次 Mocha 测试之前运行

Posted

技术标签:

【中文标题】确保 Express 应用程序在每次 Mocha 测试之前运行【英文标题】:Ensuring Express App is running before each Mocha Test 【发布时间】:2013-09-27 07:34:21 【问题描述】:

我正在使用 ExpressJS、NodeJS、Mongoose 和 Mocha 开发 REST API。

问题是我有一个 app.coffee 文件,它负责设置 ExpressJS 并连接到 Mongoose。我设置的方式是首先连接 Mongoose,如果连接成功,则启动 ExpressJS 应用程序。

问题是在设置 Mocha 时,我需要确保 app.coffee 中存在的 ExpressJS 应用程序在执行任何测试用例之前完全启动成功,包括所有异步代码。

为此,我创建了一个 test_helper.coffee 并将以下代码放入其中,但是,即使 app.coffee 中的代码尚未完全执行,测试用例也会开始执行,这实际上是有意义的:

before (done) ->
  require(__dirname + '/../src/app')
  done()

简而言之,我想确保 ExpressJS 应用在执行任何测试用例之前已完全完成设置。

我该怎么做?

【问题讨论】:

我也遇到过类似的问题。我重新设计了我的快速服务器,它现在不需要等待数据库连接或任何类型的异步初始化。这也是 REST 服务器的理想选择,因为即使没有请求进入 REST 环境,也不需要连接到数据库。此外,每次调用不一定需要所有模型。就我而言,我会在必要时初始化数据库,几乎每个调用都会这样做。 @Kamrul:你能分享一些示例代码吗? 【参考方案1】:

我迟到了,但我发现为快速应用设置 mocha 测试套件的最佳方法是让我的 app.js 或 server.js 文件导出应用对象,如下所示:

var mongoose = require('mongoose');
var express = require('express');
require('express-mongoose');

var env = process.env.NODE_ENV || 'development';
var config = require('./config/config')[env];

var models = require('./app/models');
var middleware = require('./app/middleware');
var routes = require('./app/routes');

var app = express();

app.set('port', process.env.PORT || config.port || 3000);
app.set('views', __dirname + '/app/views');
app.set('view engine', 'jade');

// database
mongoose.connect(config.db);

// middleware
middleware(app);

// Application routes
routes(app);

app.listen(app.get('port'));
console.log('Express server listening on port ' + app.get('port'));

// export app so we can test it
exports = module.exports = app;

确保您的配置文件具有不同的环境,例如开发、测试、生产设置:

var path = require('path');
var rootPath = path.normalize(__dirname + '/..');

module.exports = 
  development: 
    db: 'mongodb://localhost/my_dev_db',
    port: 3000
  ,
  test: 
    db: 'mongodb://localhost/my_test_db',
    port: 8888
  ,
  production: 
    // ...
  

然后在您的测试文件中,您可以继续并要求您的应用程序,它将连接到正确的数据库和正确的端口:

var should = require('chai').should();
var request = require('supertest');
var mongoose = require('mongoose');

var app = require('../app');
var agent = request.agent(app);

var User = mongoose.model('User');

    // get users
    describe('GET /api/users', function() 
      it('returns users as JSON', function(done) 
        agent
        .get('/api/users')
        .expect(200)
        .expect('Content-Type', /json/)
        .end(function(err, res) 
          if (err) return done(err);
          res.body.should.have.property('users').and.be.instanceof(Array);
          done();
        );
      );
    );

最后,要启动整个怪物,请将其包含在您的 package.json 中(确保在您的 devDependencies 中有 nodemon 和 mocha):

"scripts": 
    "start": "NODE_ENV=development ./node_modules/.bin/nodemon app.js",
    "test": "NODE_ENV=test ./node_modules/.bin/mocha --reporter spec test/**.js"
  

现在您可以使用npm test 启动您的测试套件,并使用npm start 启动您的应用程序。

希望对您有所帮助! ps:我从这个惊人的例子中学到的大部分东西: https://github.com/madhums/node-express-mongoose-demo

【讨论】:

supertest 知道 expressjs 应用何时可以使用吗?当你的 http 动词初始化时,如何保证数据库可以使用? 因为我从我的 app.js 文件中导出了应用程序,并在每个测试文件的顶部都需要它 但是数据库呢? AFAIK 你必须使用类似mongoose.connection.once('open', callback) 的东西,然后添加你的路线。这意味着应用程序是真正异步初始化的。你是怎么处理的? 它在上面的代码中,我在我的 app.js 文件中使用了mongoose.connect(config.db) 在数据库准备好之前mongoose.connect 是否会阻塞?如果是,事件(“开放”等)是为了什么?【参考方案2】:

我在使用 jasmine/supertest 测试我的 express 应用程序时遇到了同样的问题。我通过在应用程序准备好时发出并仅在之后运行我的测试来解决了这个问题。这是我的目录结构

- server.js
- spec
    - setup.spec.js
    - test.spec.js

server.js 中,当您的服务器设置好并准备好运行测试时,添加app.emit('started');。并确保导出您的应用程序!在setup.spec.js 中,您可以观察要发出的事件。

server.js

const express = require('express');
const app = express();
module.exports = app; // for testing

setTimeout(() => 
    app.listen(process.env.PORT || 3000);
);

setup.spec.js

const server = require('../index');

beforeAll(done => server.on('started', done));

test.spec.js

const request = require('supertest');
const server = require('../index');

describe('test', () => 
    it('test server works', done => 
        request(server).get('/test').expect(200);
     );
);

同样的想法也适用于 mocha。 Here is an article explaining this for mocha。您只需将beforeAll 更改为before

【讨论】:

是的,这对我也有用,只需在初始化/启动服务器后添加发射,然后在 before 函数中检查 app.on - 迄今为止最佳答案 server.js 中调用setTimeout() 是一种解决方法,以避免在'beforeAll() 运行和运行之前发出'started' 事件的竞争条件准备好观看活动了吗?【参考方案3】:

可能有更直接的方法,但我使用 Grunt 来自动化我的功能测试。有一个 express 和 mocha 插件可以实现您的目标。我的 gruntfile:

'use strict';

module.exports = function (grunt) 
grunt.initConfig(
    express: 
        options: 
      , test: 
            options: 
                script: './app.js'
            
        
    
  , simplemocha: 
        options: 
            globals: ['should']
          , timeout: 8000
          , ignoreLeaks: false
          , ui: 'bdd'
          , reporter: 'tap'
        
      , all:  src: ['tests/*.test.js'] 
    
)

grunt.loadNpmTasks('grunt-express-server')
grunt.loadNpmTasks('grunt-simple-mocha')

grunt.registerTask('default', ['express:test', 'simplemocha', 'express:test:stop'])

奖励:添加 'grunt' 作为 git 预提交挂钩。这样你就不能在没有通过所有测试的情况下提交

【讨论】:

【参考方案4】:

您无需侦听端口即可测试您的应用。您可以在您的应用中使用supertest 测试库,应该没问题。

您可能需要连接到数据库或 redis 客户端。您可以在应用程序的 configure 方法中执行此操作。由于 node 缓存了模块,你可以在不同的测试模块中 require app 模块,而无需重新连接。

【讨论】:

问题是我的应用程序设置是异步的,包括当我的快速路线准备好时,所以每次尝试到达该路线时,超级测试都会失败,因为它开始得太早了。 你确实需要监听一个端口。你可以只允许 supertest 到do it for you。 You don't need to listen a port for testing your app.。哦,那是新的。那么它是如何工作的呢?【参考方案5】:

app.listen 方法接受一个回调,该回调会在一切准备就绪时运行。因此,您需要能够在那里传递完成的回调。类似的东西

before (done) ->
  var app = require(__dirname + '/../src/app')
  app.listen(3000, done)

【讨论】:

【参考方案6】:

基本上你需要做两件事。

    确保数据库在服务器侦听之前连接 确保在运行测试之前应用已启动。

框架已经有了一种通过使用事件来处理这个问题的方法。

将此添加到您的数据库连接中,这会指示 mongoose 在完成连接时发出“就绪”。

mongoose.connection.once('open', function()  
   // All OK - fire (emit) a ready event. 
   console.log('Connected to MongoDB');
   app.emit('ready'); 
);

然后指示 express 在收听之前等待“就绪”。完成后,它会发出:'appStarted'。

app.on('ready', function()  
    app.listen(PORT, function() 
        console.log("Server listing on port:",PORT); 
        app.emit("appStarted");
    ); 
); 

除了 Mocha 之外,这都是很好的做法,以确保您的服务器顺利启动。在你之前完成 Mocha 集成(我喜欢使用 Promises):

before(function()         
    return new Promise((resolve,reject) => 
        app.on("appStarted", function()
            return resolve();
        ); 
    );
);

【讨论】:

这似乎可行,因为'ready' 事件有足够的延迟,Mocha 的before() 函数已经运行正在等待'appStarted' 事件。如果数据库连接速度非常快,您可能会遇到'appStarted' 被过早触发而 Mocha 看不到它的竞争条件。

以上是关于确保 Express 应用程序在每次 Mocha 测试之前运行的主要内容,如果未能解决你的问题,请参考以下文章

使用 mocha 测试 express 服务器并使用异步初始化程序进行 supertest 调用请求两次

Node Express Mocha 测试:TypeError: chai.request is not a function

使用 Express、Socket.io 和 Node-Telegram-Bot-Api 结束 Mocha 测试

单元/集成测试 Express REST API, mongoose, mocha, sinon, chai, supertest

Mocha 测试执行时间过长

为啥 Mocha 不报告每次测试的时间?