SailsJS:如何正确地对控制器进行单元测试? [英] SailsJS: How to properly unit test controllers?

查看:49
本文介绍了SailsJS:如何正确地对控制器进行单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Been working with Sails.js and was having trouble coming up with Jasmine unit tests for a controller. If this is something obvious, please pardon my ignorance, as I've only been deep-diving into JavaScript development for the past 3-4 months.

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

In past frameworks (specifically ASP .Net MVC), we had libraries to mock out any dependencies a controller might have to, say, an external service (via dependency injection). I kind of wanted to achieve the same level of unit testability with Sails.js so that we achieve a proper "unit" test. Specifically, for my case, I have a controller action with a dependency on a service object -- I simply want to mock the response of that service.

但是,我有很多时间运行这个 Jasmine 单元测试(使用 jasmine-node 插件).我的代码如下,用于控制器及其单元测试.我现在得到的是:

However, I'm having a heck of a time getting this Jasmine unit test to run (using the jasmine-node plugin). My code is below for both the controller and its unit test. What I'm getting right now is:

  1. app 对象似乎没有在 afterEach() 中解析
  2. 对间谍和测试级变量的断言失败.
  1. The app object doesn't seem to resolve in afterEach()
  2. The assertions on the spies and the test-level variables are failing.

我的单元测试中是否有明显遗漏的内容?代码如下.感谢您的任何意见!

Is there anything blatantly obvious that I've clearly missed in my unit test? Code below. Thanks for any input!

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

推荐答案

UPDATE

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

Thinking further about the application architecture, it wasn't so much that I needed to test the request/response of the Sails.js controller -- in the context of this application, the controllers are very dumb in that they just pass through JSON objects. So, what I really needed to test was that my service was translating the external API's object to my application's internal DTO that will be used as a JSON return. In other words, it's more important for me to test the actual translation versus ensuring the controller passes it through, which we can safely assume will always be the case.

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

That being said, I switched my unit testing suite over from Jasmine to Chad's suggested combination of Mocha, Chai, and Sinon. The async hooks just look much cleaner in Mocha, imo. One added library that I used was Nock, a library designed to mock HTTP requests so I can intercept my service class' call to the API and return a stubbed object.

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

So, to recap, I ditched unit testing the controller, since it's superfluous for my use case. The important functionality I needed to test was the translation of an external API's object to my internal application's equivalent DTO.

下面针对实际服务进行单元测试.请注意,此特定测试不需要 Sinon 进行存根/模拟,因为 Nock 为我处理了这些:

Unit test below for the actual service. Note that this particular test didn't have a need for Sinon for stubbing/mocking as Nock took care of that for me:

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

这篇关于SailsJS:如何正确地对控制器进行单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆