Je suis en train d'apprendre Node.js et je joue avec Express . J'aime beaucoup le framework, cependant, j'ai du mal à comprendre comment écrire un test unitaire/d'intégration pour une route.
Etre capable de tester à l'unité des modules simples est facile et le fait avec Mocha ; Cependant, mes tests unitaires avec Express échouent car l'objet de réponse que je transmets ne conserve pas les valeurs.
Route-Function Under Test (routes/index.js):
exports.index = function(req, res){
res.render('index', { title: 'Express' })
};
Module de test unitaire: _
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");
});
});
});
Lorsque j'exécute cette opération, elle échoue pour "Erreur: fuites globales détectées: nomVue, données".
Où est-ce que je me trompe pour que cela fonctionne?
Existe-t-il un meilleur moyen pour moi de tester mon code à ce niveau?
Mettre à jour 1. Extrait de code corrigé depuis que j'ai initialement oublié "it ()".
Changer votre objet de réponse:
var response = {
viewName: ""
, data : {}
, render: function(view, viewData) {
this.viewName = view;
this.data = viewData;
}
};
Et ça va marcher.
Comme d'autres l'ont recommandé dans des commentaires, il semble que la méthode la plus efficace pour tester les contrôleurs Express est d'utiliser supertest .
Un exemple de test pourrait ressembler à ceci:
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()
});
})
});
Upside: vous pouvez tester votre pile entière en une seule fois.
Inconvénient: cela ressemble un peu aux tests d'intégration.
Le moyen le plus simple de tester HTTP avec express est de voler http assistant de TJ
Je utilise personnellement son aide
it("should do something", function (done) {
request(app())
.get('/session/new')
.expect('GET', done)
})
Si vous voulez tester spécifiquement votre objet routes, transmettez les mocks corrects.
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()
}
})
})
})
Je suis parvenu à la conclusion que le seul moyen de vraiment tester les applications express est de maintenir une grande séparation entre les gestionnaires de demandes et votre logique principale.
Ainsi, la logique de votre application doit se trouver dans des modules distincts pouvant être require
d et des unités testées, et dépendre le moins possible des classes Express Request et Response en tant que telles.
Ensuite, dans les gestionnaires de demandes, vous devez appeler les méthodes appropriées de vos classes de logique principales.
Je vais vous donner un exemple une fois la restructuration de mon application actuelle terminée!
Je suppose que quelque chose comme ça? (N'hésitez pas à bifurquer du Gist ou à commenter, je suis toujours en train d'explorer ça).
Modifier
Voici un petit exemple, en ligne. Voir The Gist pour un exemple plus détaillé.
/// 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);
});
});
si les tests unitaires avec Express 4, notez cet exemple dans 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;
});
Pour réaliser des tests unitaires au lieu des tests d'intégration, j'ai simulé l'objet de réponse du gestionnaire de demandes.
/* 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);
});
});
Ensuite, pour réaliser les tests d'intégration, vous pouvez vous moquer de endpointHandler et appeler le noeud final avec supertest .
Je me demandais cela aussi, mais spécifiquement pour les tests unitaires et non les tests d'intégration. C'est ce que je fais en ce moment,
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);
});
Où routerObj est juste {router: expressRouter, path: '/api'}
. Je charge ensuite les sous-routeurs avec var loginRouterInfo = require('./login')(express.Router({mergeParams: true}));
, puis l'application express appelle une fonction init en prenant le routeur express en tant que paramètre. InitRouter appelle ensuite router.use(loginRouterInfo.path, loginRouterInfo.router);
pour monter le sous-routeur.
Le sous-routeur peut être testé avec:
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, '/');
});