SailsJS:如何正确地对测试控制器进行单元化?

时间:2014-02-20 20:15:10

标签: unit-testing sails.js

一直在使用Sails.js,并且无法为控制器提供Jasmine单元测试。如果这是显而易见的事情,请原谅我的无知,因为我在过去3-4个月里只深入研究JavaScript开发。

在过去的框架(特别是ASP .Net MVC)中,我们有了库来模拟控制器可能具有的任何依赖关系,比如外部服务(通过依赖注入)。我有点想用Sails.js达到相同级别的单元可测试性,这样我们就可以实现正确的“单元”测试。具体来说,对于我的情况,我有一个控制器动作,依赖于服务对象 - 我只是想模拟该服务的响应。

然而,我有一段时间让这个Jasmine单元测试运行(使用jasmine-node插件)。我的代码在下面是控制器及其单元测试。我现在得到的是:

  1. app 对象似乎在afterEach()
  2. 中无法解析
  3. 对间谍和测试级变量的断言失败。
  4. 我的单元测试中是否有明显错过的任何明显的明显之处?代码如下。感谢您的任何意见!

    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);
        });
    });
    

2 个答案:

答案 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提供了开箱即用的功能。