unit-testing - SailsJS : How to properly unit test controllers?

标签 unit-testing sails.js

一直在使用 Sails.js,但在为 Controller 设计 Jasmine 单元测试时遇到了麻烦。如果这很明显,请原谅我的无知,因为在过去的 3-4 个月里我才深入研究 JavaScript 开发。

在过去的框架(特别是 ASP .Net MVC)中,我们有一些库来模拟 Controller 可能必须的任何依赖关系,例如,外部服务(通过依赖注入(inject))。我有点想用 Sails.js 实现相同级别的单元可测试性,以便我们实现适当的“单元”测试。具体来说,就我而言,我有一个依赖于服务对象的 Controller 操作——我只是想模拟该服务的响应。

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

  • 应用 对象似乎无法在 afterEach()
  • 中解析
  • 对 spy 和测试级变量的断言失败了。

  • 我在单元测试中明显遗漏了什么明显的东西吗?代码如下。感谢您的任何意见!

    用户 Controller .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);
        });
    });
    

    最佳答案

    更新

    进一步考虑应用程序架构,我不需要测试 Sails.js Controller 的请求/响应——在这个应用程序的上下文中, Controller 非常愚蠢,因为它们只是传递 JSON 对象.所以,我真正需要测试的是我的服务正在将外部 API 的对象转换为我的应用程序的内部 DTO,该 DTO 将用作 JSON 返回。换句话说,对我来说,测试实际翻译比确保 Controller 通过它更重要,我们可以放心地假设情况总是如此。

    话虽如此,我将我的单元测试套件从 Jasmine 切换到 Chad 建议的 Mocha、Chai 和 Sinon 组合。 imocha 中的异步钩子(Hook)看起来更干净,imo。我使用的一个添加库是 Nock ,一个旨在模拟 HTTP 请求的库,因此我可以拦截我的服务类对 API 的调用并返回一个 stub 对象。

    所以,回顾一下,我放弃了对 Controller 的单元测试,因为它对我的用例来说是多余的。我需要测试的重要功能是将外部 API 的对象转换为我的内部应用程序的等效 DTO。

    下面的单元测试用于实际服务。请注意,此特定测试不需要 Sinon 进行 stub /模拟,因为 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();
                });
            });
        });
    });
    

    关于unit-testing - SailsJS : How to properly unit test controllers?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21918545/

    相关文章:

    node.js - sails 水线自动增量

    java - Mockito 定义 'when' 调用答案

    c# - 无法验证对具有多个接口(interface)的模拟对象上的方法的调用

    python - 模拟 default=timezone.now 用于单元测试

    sails.js - Sails js 9.4 - Assets 未被复制

    node.js - 是否可以使用子文件夹将 sails 中的 Controller 分组?

    javascript - 子数组上的 MongoDB 查询

    java - 与 argThat 匹配仅适用于具有单个参数的方法吗?

    java - 我需要模拟函数,但它不起作用,如何在不修改src代码的情况下构建unittest?

    node.js - sails.js 水线 : maxLength not set in DB