一直在使用Sails.js,并且无法为控制器提供Jasmine单元测试。如果这是显而易见的事情,请原谅我的无知,因为我在过去3-4个月里只深入研究JavaScript开发。
在过去的框架(特别是ASP .Net MVC)中,我们有了库来模拟控制器可能具有的任何依赖关系,比如外部服务(通过依赖注入)。我有点想用Sails.js达到相同级别的单元可测试性,这样我们就可以实现正确的“单元”测试。具体来说,对于我的情况,我有一个控制器动作,依赖于服务对象 - 我只是想模拟该服务的响应。
然而,我有一段时间让这个Jasmine单元测试运行(使用jasmine-node插件)。我的代码在下面是控制器及其单元测试。我现在得到的是:
我的单元测试中是否有明显错过的任何明显的明显之处?代码如下。感谢您的任何意见!
UserController.js
var Battlefield4Service = require('../services/battlefield4Service');
module.exports = {
/**
* /user/bf4stats
*/
bf4Stats: function (req, res) {
var userName = req.param('userName');
var platform = req.param('platform');
var service = new Battlefield4Service();
service.getPlayerInfo(userName, platform,
function (data) {
// Success callback
res.json(data);
});
}
};
UserController.spec.js
var Sails = require('sails');
var userController = require('./UserController');
var FPSStatsDTO = require('../dto/fpsStatsDTO');
describe('UserController', function() {
// create a variable to hold the instantiated sails server
var app, req, res, rawObject, json;
// Setup mocked dependencies
beforeEach(function() {
// Lift Sails and start the server
Sails.lift({
log: {
level: 'error'
}
}, function(err, sails) {
app = sails;
//done(err, sails);
});
// Mocked Battlefield4Service
Battlefield4Service = {
getPlayerInfo: function (userName, platform, success) {
var dto = new FPSStatsDTO();
dto.userName = userName;
dto.platform = platform;
success(dto);
}
};
// req and res objects, mock out the json call
req = {
param: function(paramName) {
switch (paramName) {
case 'userName':
return 'dummyUser';
case 'platform':
return 'dummyPlatform';
}
}
};
res = {
json: function(object) {
rawObject = object;
json = JSON.stringify(object);
return json;
}
};
// Deploy 007
spyOn(req, 'param');
spyOn(res, 'json');
spyOn(Battlefield4Service, 'getPlayerInfo');
});
afterEach(function(){
app.lower();
});
it('Should call the Battlefield 4 Service', function() {
// Call the controller
userController.bf4Stats(req, res);
// Assertions
expect(req.param).toHaveBeenCalled();
expect(res.json).toHaveBeenCalled();
expect(Battlefield4Service.getPlayerInfo).toHaveBeenCalledWith(req.param('userName'), req.param('platform'));
expect(rawObject.userName).toEqual(req.param('userName'));
expect(rawObject.platform).toEqual(req.param('platform'));
expect(json).toNotBe(null);
expect(json).toNotBe(undefined);
});
});
答案 0 :(得分:8)
<强>更新强>
进一步思考应用程序架构,我不需要测试Sails.js控制器的请求/响应 - 在这个应用程序的上下文中,控制器非常愚蠢,因为它们只是通过通过JSON对象。所以,我真正需要测试的是我的服务是将外部API的对象转换为我的应用程序的内部DTO,它将用作JSON返回。换句话说,对我来说,测试实际翻译与确保控制器通过翻译更为重要,我们可以安全地假设情况总是如此。
话虽这么说,我将我的单元测试套件从Jasmine切换到Chad建议的Mocha,Chai和Sinon的组合。 imocha中的异步挂钩看起来更干净。我使用的一个添加的库是Nock,这是一个用于模拟HTTP请求的库,因此我可以拦截我的服务类对API的调用并返回一个存根对象。
所以,回顾一下,我放弃了测试控制器的单元,因为它对我的用例来说是多余的。我需要测试的重要功能是将外部API对象转换为我的内部应用程序的等效DTO。
以下单元测试用于实际服务。请注意,这个特定的测试并不需要Sinon用于存根/嘲弄,因为Nock为我处理了这个问题:
var Sails = require('sails');
var sinon = require('sinon'); // Mocking/stubbing/spying
var assert = require('chai').assert; // Assertions
var nock = require('nock'); // HTTP Request Mocking
var constants = require('../constants/externalSystemsConstants');
var Battlefield4Service = require('./battlefield4Service');
describe('External Services', function () {
// create a variable to hold the instantiated sails server
var app, battlefield4Service;
// Global before hook
before(function (done) {
// Lift Sails and start the server
Sails.lift({
log: {
level: 'error'
}
}, function (err, sails) {
app = sails;
done(err, sails);
});
});
// Global after hook
after(function (done) {
app.lower(done);
});
describe('Battlefield 4 Service', function () {
var userName, platform, kills, skill, deaths, killAssists, shotsHit, shotsFired;
before(function () {
// Mock data points
userName = 'dummyUser';
platform = 'ps3';
kills = 200;
skill = 300;
deaths = 220;
killAssists = 300;
shotsHit = 2346;
shotsFired = 7800;
var mockReturnJson = {
player: {
name: userName,
plat: platform
},
stats: {
kills: kills,
skill: skill,
deaths: deaths,
killAssists: killAssists,
shotsHit: shotsHit,
shotsFired: shotsFired
}
};
// Mock response from BF4 API
battlefield4Service = nock('http://' + constants.BF4_SERVICE_URI_HOST)
.get(constants.BF4_SERVICE_URI_PATH.replace('[platform]', platform).replace('[name]', userName))
.reply(200, mockReturnJson);
});
it('Should translate BF4 API data to FPSStatsDTO', function (done) {
var service = new Battlefield4Service();
service.getPlayerInfo(userName, platform, function (fpsStats) {
assert(fpsStats !== null);
assert(fpsStats !== undefined);
assert(fpsStats.kills === kills, 'kills');
assert(fpsStats.deaths === deaths, 'deaths');
assert(fpsStats.killAssists === killAssists, 'deaths')
assert(fpsStats.kdr === kills / deaths, 'kdr');
assert(fpsStats.shotsFired === shotsFired, 'shotsFired');
assert(fpsStats.shotsHit === shotsHit, 'shotsHit');
assert(fpsStats.shotsAccuracy === shotsHit / shotsFired, 'shotsAccuracy');
assert(fpsStats.userName === userName, 'userName');
assert(fpsStats.platform === platform, 'platform');
done();
});
});
});
});
答案 1 :(得分:3)
我注意到你有done()
号召唤在风帆电梯回调中的注释,而且一般情况下你将beforeEach定义为同步挂钩。您需要将beforeEach定义为异步挂钩,并确保所有设置逻辑都在sails lift回调中,如下所示:
beforeEach(function (done) {
// Lift Sails and start the server
Sails.lift({
log: {
level: 'error'
}
}, function (err, sails) {
app = sails;
// put the rest of your setup code here
done(err);
});
});
你的后期也是如此:
afterEach(function (done){
app.lower(done);
});
一旦完成,我想你可以按照你的预期进行测试。
P.S。如果您仍处于sails.js测试的早期阶段,您可能需要考虑切换到Mocha + Chai + Sinon。 Mocha允许您为测试套件配置单个before()和after()钩子,这样您只能提升一次风帆(如果您进行了大量测试,可以极大地加快测试速度)。 Chai + Sinon只提供了一个断言库和模拟库,Jasmine提供了开箱即用的功能。