如何单元测试表达路由器的路由

cus*_*ice 24 javascript unit-testing node.js express chai

我是Node和Express的新手,我正在尝试对我的路由/控制器进行单元测试.我把我的路线与控制器分开了.我该如何测试我的路线?

配置/ express.js

  var app = express();
  // middleware, etc
  var router = require('../app/router')(app);
Run Code Online (Sandbox Code Playgroud)

应用程序/路由器/ index.js

  module.exports = function(app) {
    app.use('/api/books', require('./routes/books'));
  };
Run Code Online (Sandbox Code Playgroud)

应用程序/路由器/路由/ books.js

  var controller = require('../../api/controllers/books');
  var express = require('express');
  var router = express.Router();

  router.get('/', controller.index);

  module.exports = router;
Run Code Online (Sandbox Code Playgroud)

应用程序/ API /控制器/ books.js

// this is just an example controller
exports.index = function(req, res) {
    return res.status(200).json('ok');
};
Run Code Online (Sandbox Code Playgroud)

应用程序/测试/ API /路由/ books.test.js

  var chai = require('chai');
  var should = chai.should();
  var sinon = require('sinon');

  describe('BookRoute', function() {

  });
Run Code Online (Sandbox Code Playgroud)

Jam*_*mie 15

如果您只想对路由的存在及其方法进行单元测试,您可以执行以下操作:

auth.router.js

import { Router } from 'express';

const router = Router();

router.post('/signup', signupValidation, signupUser);
router.post('/login', loginValidation, loginUser);
router.post('/reset', resetValidation, setPasswordReset);

export default router;
Run Code Online (Sandbox Code Playgroud)

auth.router.spec.js

test('has routes', () => {
  const routes = [
    { path: '/signup', method: 'post' },
    { path: '/login', method: 'post' },
    { path: '/reset', method: 'post' },
  ]

it.each(routes)('`$method` exists on $path', (route) => {
  expect(router.stack.some((s) => Object.keys(s.route.methods).includes(route.method))).toBe(true)
  expect(router.stack.some((s) => s.route.path === route.path)).toBe(true)
})
Run Code Online (Sandbox Code Playgroud)

注意:示例测试名称中 使用$variables仅适用于 Jest ^27.0.0

编辑:感谢 Keith Yeh 建议将其纳入each()声明中。我已相应更新了代码,旧代码如下:

auth.router.spec.js (OLD)

import router from '../auth.router';

test('has routes', () => {
  const routes = [
    { path: '/signup', method: 'post' },
    { path: '/login', method: 'post' },
    { path: '/reset', method: 'post' }
  ]

  routes.forEach((route) => {
    const match = router.stack.find(
      (s) => s.route.path === route.path && s.route.methods[route.method]
    );
    expect(match).toBeTruthy();
  });
});
Run Code Online (Sandbox Code Playgroud)

  • 已投赞成票。执行单元测试的唯一答案。其他答案正在做集成测试。 (4认同)

iro*_*hon 9

码:

配置/ express.js

var app = express();
// middleware, etc
var router = require('../app/router')(app);

module.exports = app;
Run Code Online (Sandbox Code Playgroud)

应用程序/测试/ API /路由/ books.test.js

var chai = require('chai');
var should = chai.should();
var sinon = require('sinon');
var request = require('supertest');
var app = require('config/express');

describe('BookRoute', function() {
    request(app)
        .get('/api/books')
        .expect('Content-Type', /json/)
        .expect('Content-Length', '4')
        .expect(200, "ok")
        .end(function(err, res){
           if (err) throw err;
        });
});
Run Code Online (Sandbox Code Playgroud)

注意事项:

如果你的服务器在一组测试开始时需要一个初始状态(因为你正在执行改变服务器状态的调用),你需要编写一个函数来返回一个新配置的应用程序和每个组的开头.试验.有一个NPM库:https://github.com/bahmutov/really-need,它允许您需要一个新实例化的服务器版本.

  • 这是集成测试,而不是单元测试。Supertest 在侦听端口上启动正在运行的服务器。据我所知,Express 无法将路由映射到 (req, res) 函数来测试底层逻辑,而不是拉入整个服务器。疯狂。 (11认同)

Pet*_*ght 5

这很有趣,因为您已将控制器与路由器分开。我认为,注释中提到的其他StackOverflow文章是测试控制器的好方法。单元测试要牢记的一点是,您到底要进行什么测试。您不需要编写测试来测试快速库,因为大概它具有自己的单元测试。因此,您只需要测试对库的调用即可。因此对于书本路线,您只需要测试以下代码即可:

router.get('/', controller.index);
Run Code Online (Sandbox Code Playgroud)

我环顾四周,看看是否有一种明显的方法可以从快递库中获取路线列表,但没有找到。您可能只需要查看库本身并检查其内部结构,以查看是否正确设置了路由。但是,另一个选择是模拟它,并检查您是否正确调用了它。

这将变得非常复杂,因为您需要模拟Javascript的一些基本部分才能测试这一行代码。这是我的操作方式:

describe('BookRoute', function() {
  it("should route / to books controller index", function() {
    var controller = require('../../../api/controllers/books');
    var orig_this = this;
    var orig_load = require('module')._load;
    var router = jasmine.createSpyObj('Router', ['get']);
    var express = jasmine.createSpyObj('express', ['Router']);
    express.Router.and.returnValues(router);
    spyOn(require('module'), '_load').and.callFake(function() {
      if (arguments[0] == 'express') {
        return express;
      } else {
        return orig_load.apply(orig_this, arguments);
      }
    });
    require("../../../router/routes/books");
    expect(router.get).toHaveBeenCalledWith('/', controller.index);
  });
});
Run Code Online (Sandbox Code Playgroud)

这里发生的事情是我使用Jasmine的spyOn函数来对module.js中的_load函数进行spyOn处理,这是处理所有require调用的原因。这样一来,当我们需要books路由器并调用require('express')时,我们可以返回使用jasmine.createSpyObj创建的Express SpyObj。一旦我们用间谍对象替换了express,便可以让它返回Router SpyObj,这将使我们可以窥探router.get。然后,我们可以检查以确保使用'/'和controller.index调用了它。

如果您想经常使用它,可以将其变成某种实用程序。

我通常通过使用一种更加面向对象的方法来避免很多事情,或者我在任何可以模拟测试的对象周围传递一些对象,或者可以使用某种依赖注入,例如Angular用法。


Pat*_*ard 2

在测试我自己的服务器端点时,我发现这个博客非常有洞察力。

他在博客中提到:

  • 如何使用端点测试库supertest

  • 如何在每次端点测试之前和之后以编程方式启动和拆除带有所需路由的 Express 服务器。(他还解释了为什么你想这样做)。

  • 如何避免常见的问题,需要缓存单元测试中所需的模块,从而导致意想不到的后果。

希望这可以帮助。祝您好运,如果您有任何其他问题,请告诉我。

  • 博客点有一些关于如何使用 supertest 来测试 API 端点的很好的示例。然而,我觉得需要一些评论,因为我不完全同意作者的观点。在每次测试之间启动和停止服务器是一个糟糕的设计。如果您正在测试的是 RESTful API,那么服务器无论如何都应该是无状态的。如果它是具有状态的应用程序,您可以销毁每个测试之间的会话以实现相同的目的。另外,作者似乎不了解 JavaScript 堆栈的工作原理,这导致他得出了错误的结论。 (6认同)