如何使用Mocha测试我的Express应用程序?


67

我刚刚在我的express应用程序中添加了shouldjs和mocha进行测试,但是我想知道如何测试我的应用程序。我想这样做:

app = require '../app'
routes = require '../src/routes'

describe 'routes', ->
  describe '#show_create_user_screen', ->
    it 'should be a function', ->
      routes.show_create_user_screen.should.be.a.function
    it 'should return something cool', ->
      routes.show_create_user_screen().should.be.an.object

当然,测试套件中的最后一个测试只是告诉med,res.render函数(在show_create_user_screen中调用)未定义,可能是因为服务器未运行且配置尚未完成。所以我想知道其他人如何设置他们的测试?


就像要补充的是,上面的示例是简短简短的,因此已发布。通常,我会测试在调用路由器功能之一后是否已设置/调用给定req / res对象上的适当功能或值。对于下面的答案是足够的。一个人不应该测试路由器的功能,那就是网络框架的工作。
Robin Heggelund Hansen 2012年

Answers:


33

OK,首先,尽管您可能不想做测试路由代码的操作,但通常来说,请尝试将纯JavaScript代码(类或函数)与Express或您使用的任何框架分离的有趣的业务逻辑分开。使用香草摩卡咖啡测试来测试。一旦实现,如果您想真正测试在Mocha中配置的路由,则需要将模拟req, res参数传递到中间件函数中,以模仿Express / Connect与中间件之间的接口。

对于一个简单的例子,您可以创建一个模拟res对象,其render功能类似于以下内容。

describe 'routes', ->
  describe '#show_create_user_screen', ->
    it 'should be a function', ->
      routes.show_create_user_screen.should.be.a.function
    it 'should return something cool', ->
      mockReq = null
      mockRes =
        render: (viewName) ->
          viewName.should.exist
          viewName.should.match /createuser/

      routes.show_create_user_screen(mockReq, mockRes).should.be.an.object

同样,仅FYI中间件函数不需要返回任何特定值,这是它们req, res, next对测试中应重点关注的参数所做的事情。

这是您在注释中要求的一些JavaScript。

describe('routes', function() {
    describe('#show_create_user_screen', function() {
      it('should be a function', function() {
        routes.show_create_user_screen.should.be.a["function"];
      });
      it('should return something cool', function() {
        var mockReq = null;
        var mockRes = {
          render: function(viewName) {
            viewName.should.exist;
            viewName.should.match(/createuser/);
          }
        };
        routes.show_create_user_screen(mockReq, mockRes);
      });
    });
  });

1
模拟不能给您带来的一件事就是可以防止您正在使用的模块的api更改。例如,如果快速更新并更改了渲染的名称,则您不受保护。理想情况下,您也要对其进行测试,但是有时集成+单元测试可以一次测试很多代码,这取决于您的外观是好是坏。编辑:虽然我真的很喜欢这种模拟方法,但它确实很轻巧。
timoxley

63
可能还请随时添加已编译的js,有些人不熟悉阅读coffeescript。
Julius F

这不是测试实现细节吗?您实际上是想测试“响应”对象在返回时所包含的内容-如果将来您不使用“渲染”方法来执行此操作,例如在重构期间您的测试将失败并且不向您显示重构的代码可以,因为您必须重写测试?只是一个想法 !否则,这是模拟响应对象的聪明方法。
SimonB 2014年

超级测试是进行更多端到端测试的另一种方法。两者都有其用途。
彼得·里昂斯

66

connect.js测试套件中找到了替代方案

他们正在使用supertest测试连接应用程序,而无需将服务器绑定到任何端口,也无需使用实体模型。

这是connect的静态中间件测试套件的摘录(使用mocha作为测试运行程序,并使用supertest进行断言)

var connect = require('connect');

var app = connect();
app.use(connect.static(staticDirPath));

describe('connect.static()', function(){
  it('should serve static files', function(done){
    app.request()
    .get('/todo.txt')
    .expect('contents', done);
  })
});

这也适用于快速应用


我只能接受一个答案,否则也将被接受=)
Robin Heggelund Hansen 2012年

1
app.request在最新的express / connect中对我不起作用,因此我已在github.com/visionmedia/supertest上
Aaron

关于supertest看起来的条款具有误导性。在连接代码中似乎没有提及它。无论如何,Alexandru的答案看起来比其他答案更好。
yanychar 2014年

下面是连接的部分用途supertestgithub.com/senchalabs/connect/blob/...
费利克斯Saparelli

23

您可以尝试SuperTest,然后处理服务器的启动和关闭:

var request = require('supertest')
  , app     = require('./anExpressServer').app
  , assert  = require("assert");

describe('POST /', function(){
  it('should fail bad img_uri', function(done){
    request(app)
        .post('/')
        .send({
            'img_uri' : 'foobar'
        })
        .expect(500)
        .end(function(err, res){
            done();
        })
  })
});

SuperTest为我工作至少一个项目。为了胜利!
深度元素2014年

1
我想知道supertest和之间有什么区别chaihttp
4Z4T4R

7

mocha附带有进行bdd测试的before,beforeEach,after和afterEach。在这种情况下,您应该在describe调用中使用before。

describe 'routes' ->
  before (done) ->
    app.listen(3000)
    app.on('connection', done)

5

我发现设置TestServer类以用作辅助程序以及辅助http客户端,并向真正的http服务器发出真实请求是最简单的。在某些情况下,您可能想模拟和存根这些东西。

// Test file
var http = require('the/below/code');

describe('my_controller', function() {
    var server;

    before(function() {
        var router = require('path/to/some/router');
        server = http.server.create(router);
        server.start();
    });

    after(function() {
        server.stop();
    });

    describe("GET /foo", function() {
        it('returns something', function(done) {
            http.client.get('/foo', function(err, res) {
                // assertions
                done();
            });
        });
    });
});


// Test helper file
var express    = require('express');
var http       = require('http');

// These could be args passed into TestServer, or settings from somewhere.
var TEST_HOST  = 'localhost';
var TEST_PORT  = 9876;

function TestServer(args) {
    var self = this;
    var express = require('express');
    self.router = args.router;
    self.server = express.createServer();
    self.server.use(express.bodyParser());
    self.server.use(self.router);
}

TestServer.prototype.start = function() {
    var self = this;
    if (self.server) {
        self.server.listen(TEST_PORT, TEST_HOST);
    } else {
        throw new Error('Server not found');
    }
};

TestServer.prototype.stop = function() {
    var self = this;
    self.server.close();
};

// you would likely want this in another file, and include similar 
// functions for post, put, delete, etc.
function http_get(host, port, url, cb) {
    var options = {
        host: host,
        port: port,
        path: url,
        method: 'GET'
    };
    var ret = false;
    var req = http.request(options, function(res) {
        var buffer = '';
        res.on('data', function(data) {
            buffer += data;
        });
        res.on('end',function(){
            cb(null,buffer);
        });
    });
    req.end();
    req.on('error', function(e) {
        if (!ret) {
            cb(e, null);
        }
    });
}

var client = {
    get: function(url, cb) {
        http_get(TEST_HOST, TEST_PORT, url, cb);
    }
};

var http = {
    server: {
        create: function(router) {
            return new TestServer({router: router});
        }
    },

    client: client
};
module.exports = http;

刚意识到我错过了您的问题的重点,但是也许无论如何这将有所帮助。我个人不自行测试路由器功能。我只是通过HTTP请求测试服务器基本上完成了预期的工作,然后分别测试所有业务逻辑,因为无论如何它们都在控制器外部的文件中。
布莱恩·多诺万

您已经有了参考,path/to/some/router查看该文件的内容将很有帮助。
ryonlife 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.