我正在学习Node.js并且一直在玩Express。真的很喜欢框架;但是,我无法弄清楚如何为路线编写单元/集成测试。
能够对简单模块进行单元测试很容易,并且已经使用Mocha进行单元测试;但是,我的单元测试使用Express失败,因为我传入的响应对象没有保留值。
测试中的路由功能(routes / index.js):
exports.index = function(req, res){
res.render('index', { title: 'Express' })
};
单元测试模块:
var should = require("should")
, routes = require("../routes");
var request = {};
var response = {
viewName: ""
, data : {}
, render: function(view, viewData) {
viewName = view;
data = viewData;
}
};
describe("Routing", function(){
describe("Default Route", function(){
it("should provide the a title and the index view name", function(){
routes.index(request, response);
response.viewName.should.equal("index");
});
});
});
当我运行它时,它失败了“错误:检测到全局泄漏:viewName,data”。
我哪里出错,以便我可以开始工作?
我是否有更好的方法在这个级别对我的代码进行单元测试?
更新 1.纠正了代码片段,因为我最初忘记了“it()”。
答案 0 :(得分:34)
正如其他人在评论中所建议的那样,看起来测试Express控制器的规范方法是通过supertest。
示例测试可能如下所示:
describe('GET /users', function(){
it('respond with json', function(done){
request(app)
.get('/users')
.set('Accept', 'application/json')
.expect(200)
.end(function(err, res){
if (err) return done(err);
done()
});
})
});
上升:您可以一次性测试整个堆栈。
下行:感觉和行为有点像集成测试。
答案 1 :(得分:20)
更改您的回复对象:
var response = {
viewName: ""
, data : {}
, render: function(view, viewData) {
this.viewName = view;
this.data = viewData;
}
};
它会起作用。
答案 2 :(得分:19)
使用express测试HTTP的最简单方法是窃取TJ's http helper
it("should do something", function (done) {
request(app())
.get('/session/new')
.expect('GET', done)
})
如果你想专门测试你的路线对象,那么传入正确的模拟
describe("Default Route", function(){
it("should provide the a title and the index view name", function(done){
routes.index({}, {
render: function (viewName) {
viewName.should.equal("index")
done()
}
})
})
})
答案 3 :(得分:16)
我得出的结论是,真正单元测试表达应用程序的唯一方法是在请求处理程序和核心逻辑之间保持大量的分离。
因此,您的应用程序逻辑应该位于可以require
d和单元测试的单独模块中,并且对Express请求和响应类的依赖性最小。
然后在请求处理程序中,您需要调用核心逻辑类的适当方法。
一旦我完成了当前应用程序的重组,我就会举一个例子!
我想像this?之类的东西(随意分享要点或评论,我仍在探索这个)。
修改强>
这是一个很小的例子,内联。有关更详细的示例,请参阅the gist。
/// usercontroller.js
var UserController = {
_database: null,
setDatabase: function(db) { this._database = db; },
findUserByEmail: function(email, callback) {
this._database.collection('usercollection').findOne({ email: email }, callback);
}
};
module.exports = UserController;
/// routes.js
/* GET user by email */
router.get('/:email', function(req, res) {
var UserController = require('./usercontroller');
UserController.setDB(databaseHandleFromSomewhere);
UserController.findUserByEmail(req.params.email, function(err, result) {
if (err) throw err;
res.json(result);
});
});
答案 4 :(得分:6)
如果使用快递4进行单元测试,请注意gjohnson 中的此示例:
var express = require('express');
var request = require('supertest');
var app = express();
var router = express.Router();
router.get('/user', function(req, res){
res.send(200, { name: 'tobi' });
});
app.use(router);
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function(err, res){
if (err) throw err;
});
答案 5 :(得分:1)
为了实现单元测试而不是集成测试,我模拟了请求处理程序的响应对象。
/* app.js */
import endpointHandler from './endpointHandler';
// ...
app.post('/endpoint', endpointHandler);
// ...
/* endpointHandler.js */
const endpointHandler = (req, res) => {
try {
const { username, location } = req.body;
if (!(username && location)) {
throw ({ status: 400, message: 'Missing parameters' });
}
res.status(200).json({
location,
user,
message: 'Thanks for sharing your location with me.',
});
} catch (error) {
console.error(error);
res.status(error.status).send(error.message);
}
};
export default endpointHandler;
/* response.mock.js */
import { EventEmitter } from 'events';
class Response extends EventEmitter {
private resStatus;
json(response, status) {
this.send(response, status);
}
send(response, status) {
this.emit('response', {
response,
status: this.resStatus || status,
});
}
status(status) {
this.resStatus = status;
return this;
}
}
export default Response;
/* endpointHandler.test.js */
import Response from './response.mock';
import endpointHandler from './endpointHander';
describe('endpoint handler test suite', () => {
it('should fail on empty body', (done) => {
const res = new Response();
res.on('response', (response) => {
expect(response.status).toBe(400);
done();
});
endpointHandler({ body: {} }, res);
});
});
然后,要实现集成测试,您可以模拟您的endpointHandler并使用supertest调用端点。
答案 6 :(得分:0)
我也想知道这一点,但是专门针对单元测试而不是集成测试。这就是我现在正在做的事情,
test('/api base path', function onTest(t) {
t.plan(1);
var path = routerObj.path;
t.equals(path, '/api');
});
test('Subrouters loaded', function onTest(t) {
t.plan(1);
var router = routerObj.router;
t.equals(router.stack.length, 5);
});
routerObj只是{router: expressRouter, path: '/api'}
。
然后,我使用var loginRouterInfo = require('./login')(express.Router({mergeParams: true}));
加载子路由器,然后快速应用程序调用init-function接收快速路由器作为参数。然后,initRouter调用router.use(loginRouterInfo.path, loginRouterInfo.router);
来挂载子路由器。
子路由器可以通过以下方式进行测试:
var test = require('tape');
var routerInit = require('../login');
var express = require('express');
var routerObj = routerInit(express.Router());
test('/login base path', function onTest(t) {
t.plan(1);
var path = routerObj.path;
t.equals(path, '/login');
});
test('GET /', function onTest(t) {
t.plan(2);
var route = routerObj.router.stack[0].route;
var routeGetMethod = route.methods.get;
t.equals(routeGetMethod, true);
var routePath = route.path;
t.equals(routePath, '/');
});
答案 7 :(得分:0)
在我的情况下,我唯一要测试的是是否已调用正确的处理程序。我想使用supertest来简化向路由中间件发出请求的简便性。我正在使用Typescript a,这是对我有用的解决方案
// ProductController.ts
import { Request, Response } from "express";
class ProductController {
getAll(req: Request, res: Response): void {
console.log("this has not been implemented yet");
}
}
export default ProductController
路线
// routes.ts
import ProductController from "./ProductController"
const app = express();
const productController = new ProductController();
app.get("/product", productController.getAll);
测试
// routes.test.ts
import request from "supertest";
import { Request, Response } from "express";
const mockGetAll = jest
.fn()
.mockImplementation((req: Request, res: Response) => {
res.send({ value: "Hello visitor from the future" });
});
jest.doMock("./ProductController", () => {
return jest.fn().mockImplementation(() => {
return {
getAll: mockGetAll,
};
});
});
import app from "./routes";
describe("Routes", () => {
beforeEach(() => {
mockGetAll.mockImplementation((req: Request, res: Response) => {
res.send({ value: "You can also change the implementation" });
});
});
it("GET /product integration test", async () => {
const result = await request(app).get("/product");
expect(mockGetAll).toHaveBeenCalledTimes(1);
});
it("GET an undefined route should return status 404", async () => {
const response = await request(app).get("/random");
expect(response.status).toBe(404);
});
});
我有一些问题可以使模拟工作。但是使用jest.doMock和示例中显示的特定顺序可以使其正常工作。