使用 Jasmine/Karma 对服务进行单元测试不会注入该服务的副本

Using Jasmine/Karma To Unit Test A Service Won't Inject A Copy Of The Service

警告,这是一个工作项目,因此信息有限。如果需要更多信息,请询问,如果我觉得合适,我会添加。

我花了大约一天半的时间来解决我的单元测试无法加载我正在尝试测试的服务副本的问题。 app.js(包含主应用 "CC" 的相关部分:

angular
  .module("CC", [
    "ui.router",
    "angularMoment",
    "focus-if",
    "ngCapsLock",
    "CC.AuthInterceptor",
    "CC.Filters",
    "CC.Body",
    "CC.Login",
    "CC.Courtyard",
    "CC.SocketService",
    "CC.HeaderNavbar",
    "CC.footerBar",
    "CC.AcademicHall",
    "CC.ResourceCenter",
    "CC.UserSettings",
    "CC.imageOnLoad",
    "CC.recommendedResources",
    "CC.Constants"
  ])
  .config(ConfigureCookies)
  .config(ConfigureResources)
  .config(ConfigureInterceptors)
  .config(ConfigureDefaultPage)
  .config(ConfigureHTML5Mode)
  .config(ConfigureRouter)
  .run(AuthRedirect);

相关模块(如我所见,这可能是我的问题)是 CC.Constants(由 Grunt 编写的包含包含相关 URL 的常量的模块)和 CC.SocketService(实际服务是已测试)。

CC.Constants 看起来像这样:

(function () { 
    "use strict";
    angular.module('CC.Constants', [])
        .constant('apiUrl', "<URL1>")
        .constant('socialUrl', "<URL2>"); 
}());

这就是我可以从 SocketService 向您展示的内容,我觉得这一切都是相关的,考虑到我此时不尝试明确测试它,只是进行测试设置。

(function() {
  "use strict";
  function SocketService($rootScope, $cookies, $log, $state, jwtHelper, moment, socialUrl) {
    var socket = {};
    socket.socket = {}; //This holds the socket connection after a specific function is run
    socket.data = {};
    socket.data.sortedMessages = [];
    socket.data.userChannels = [];
    socket.supportFunctions = {};
    socket.supportFunctions.sortPost = function(post) {
      for (var x = socket.data.userChannels.length - 1; x >= 0; x--) {
        if (post.courtyard_post_to === socket.data.userChannels[x].pk) {
          socket.data.userChannels[x].post_to_channel.push(post);
          return;
        }
      }
    };
    /* A bunch of other functions live here all under the socket object in one way or another */
    return socket;
  }
  SocketService.$inject = ["$rootScope", "$cookies", "$log", "$state", "jwtHelper", "moment", "socialUrl"];
  angular
    .module("CC.SocketService", [
      "ngCookies",
      "ui.router",
      "angularMoment",
      "angular-jwt"
    ])
    .factory("SocketService", SocketService);
})();

下面是现在的测试。

(function () {
    "use strict";

    describe("ConnectedCampus.SocketService", function () {
        var $cookies,
            $state,
            Socket;
        var channel1 = {an imitation channel goes here};
        var channel2 = {an imitation channel goes here};
        var channel3 = {an imitation channel goes here};
        var postToChannel1 = {an imitation post goes here};

        beforeEach(function (){
            $cookies = jasmine.createSpyObj("$cookies", ["get", "put"]);
            $cookies.get.and.returnValue("<valid Login Token Goes Here>");
            $state = jasmine.createSpyObj("$state", ["go"]);
            module("CC");
            module("CC.SocketService");
            module(function ($provide) {
                $provide.value("$cookies", $cookies);
                $provide.value("$state", $state);
                $provide.constant("socialUrl", "fakeAddress");
            });
            inject(function (SocketService) {
                Socket = SocketService;
            })
        });
        describe("Support Functions", function () {
            it("socket.supportFunctions.sortPost should push a new post into the channel's posts array.", function (done) {
                console.log("Socket");
                console.log(Socket);//this logs "undefined"
                Socket.data.userChannels = [channel1, channel2, channel3];
                Socket.supportFunctions.sortPost(postToChannel1);
                expect(Socket.data.userChannels[0].post_to_channel).toContain(postToChannel1);
                done();
            })
        });
    });
}());

这是堆栈跟踪:

SocketService@<path to app>/app/socket/socket.js:320:25 //the file is only 208 lines long
invoke@<path to app>/app/libs/angular/angular.js:4535:22
enforcedReturnValue@<path to app>/app/libs/angular/angular.js:4387:43
invoke@<path to app>/app/libs/angular/angular.js:4535:22
<path to app>/app/libs/angular/angular.js:4352:43
getService@<path to app>/app/libs/angular/angular.js:4494:46
invoke@<path to app>/app/libs/angular/angular.js:4526:23
workFn@<path to app>/app/libs/angular-mocks/angular-mocks.js:2517:26
inject@<path to app>/app/libs/angular-mocks/angular-mocks.js:2489:41
<path to app>/app/socket/socket_test.js:321:19
<path to app>/app/login/login_test.js:49:17 //I don't know why these are here, login tests are passing
<path to app>/app/login/login_test.js:43:17 //I don't know why these are here, login tests are passing
<path to app>/app/courtyard/courtyard_test.js:474:21
TypeError: undefined is not an object (evaluating 'Socket.data') in <path to app>/app/socket/socket_test.js (line 337) //Line 337 is the line 'Socket.data.userChannels = [channel1, channel2, channel3];'
/home/anton/git/connected_campus-ng/app/socket/socket_test.js:337:23
<path to app>/app/login/login_test.js:49:17 //I don't know why these are here, login tests are passing
<path to app>/app/login/login_test.js:43:17 //I don't know why these are here, login tests are passing
<path to app>/app/courtyard/courtyard_test.js:474:21 //I don't know why these are here, courtyard tests are passing

这是 Karma 通过 grunt 看到的我的文件:

files: [
    "app/libs/angular/angular.js",
    "app/libs/moment/moment.min.js",
    "app/libs/**/*.js",
    "app/app.js",
    "app/constants.js",
    "app/**/*.js"//SocketService lives in 'app/socket/socket.js'
],

我试过的一些东西:

  1. 我试过下划线表示法
  2. 我已经试过 beforeEach 在里面的 describe
  3. 我试过按照(假定的)正确顺序将 beforeEach 分成多个 beforeEach
  4. 我已经为个人测试尝试了内联注入
  5. 我试过使用 $injector.get() 然后注入每个需求
  6. 我试过了 $injector.get("SocketService")

这是我的依赖项的版本号(从 bower 和包中提取)。

鲍尔:

"angular": "1.4.9",
"angular-cookies": "1.4.9",
"angular-resource": "1.4.9",
"angular-animate": "1.4.9",
"angular-ui-router": "0.2.18",
"ng-file-upload-shim": "12.0.1",
"ng-file-upload": "12.0.1",
"bootstrap":"3.3.6",
"angular-loader": "1.4.9",
"angular-mocks": "1.4.9",
"angular-bootstrap": "1.1.2",
"fontawesome": "4.5.0",
"moment": "2.12.0",
"angular-moment": "1.0.0-beta.5",
"socket.io-client": "1.4.6",
"angular-jwt": "0.0.9",
"ng-focus-if": "1.0.5",
"ng-caps-lock": "1.0.2",
"animate.css": "3.5.1",
"angular-media-queries": "0.5.1"

包裹:

"bower": "1.7.9",
"bower-installer": "1.2.0",
"grunt": "1.0.1",
"grunt-contrib-concat": "1.0.1",
"grunt-contrib-cssmin": "1.0.1",
"grunt-contrib-htmlmin": "1.4.0",
"grunt-contrib-jshint": "1.0.0",
"grunt-contrib-sass": "1.0.0",
"grunt-contrib-watch": "1.0.0",
"grunt-githooks": "0.6.0",
"grunt-karma": "1.0.0",
"grunt-ng-annotate": "2.0.2",
"grunt-ng-constant": "2.0.1",
"http-server": "0.9.0",
"jasmine-core": "2.4.1",
"jshint-stylish": "2.2.0",
"karma": "0.13.22",
"karma-coverage": "1.0.0",
"karma-ie-launcher": "1.0.0",
"karma-jasmine": "1.0.2",
"karma-junit-reporter": "1.0.0",
"karma-phantomjs-launcher": "1.0.0",
"karma-safari-launcher": "1.0.0",
"phantomjs-prebuilt": "2.1.7",
"protractor": "3.3.0",
"shelljs": "0.7.0"

非常感谢任何帮助。我不明白这一点,我之前测试过服务,实际的 Socket 代码有效。同样,如果您需要更多信息,请随时询问,但我可能无法提供给您。

我发现了问题,它不在我透露的范围内。我不确定 为什么 我的更改有效,但在我的情况下它确实有效,如果有人有同样的问题,我想把我的修复放在那里。

SocketService 是一种在个人逻辑中包装 Socket.IO 套接字并将 javascript 全局 io.connect() 和其他 io/socket 功能引入 angular 生态系统。我拥有它,以便在调用事件 socket.createConnection() 时创建实际连接,该事件在实际代码中有效,但由于某种原因在测试中无效。我将连接设置为一个事件,每次您初始化服务时都会触发该事件,也就是文件顶部附近的 socket.socket = io.connect(socialUrl),这似乎可以解决问题。所以现在文件的顶部看起来像:

function SocketService($rootScope, $cookies, $log, $state, jwtHelper, moment, socialUrl) {
    /* The object to be returned by the service, includes:
     * .data(actual data),
     * .supportFunctions(functions to sort and place data),
     * .callbackFunctions(callbacks to call on 'on' events
     * .socket which contains the actual socket connection */
    var socket = {};
    /* Object to hold socket connection */
    socket.socket = io.connect(socialUrl); //This is the important change
    /* Holds all the data gathered so far by the socket */
    socket.data = {};
    /* Holds all the sorted messages */
    socket.data.sortedMessages = [];
    /* Holds all the channels the user is a member of */
    socket.data.userChannels = [];
    /* Object holding all the support functions necessary for the data to be manipulated as expected */
    socket.supportFunctions = {};

现在可以使用包含下划线标记的声明的外部描述。如下所示,此测试现在通过意味着更改有效:

(function () {
    "use strict";

    describe("ConnectedCampus.SocketService", function () {
        /* jshint unused: false */
        var $rootScope,
            $cookies,
            $log,
            $state,
            jwtHelper,
            moment,
            SocketService;
        var channel1 = <imitation channel object>;
        var channel2 = <imitation channel object>;
        var channel3 = <imitation channel object>;
        var postToChannel1 = <imitation post to 1st channel>;

        beforeEach(function (){
            $cookies = jasmine.createSpyObj("$cookies", ["get", "put"]);
            $cookies.get.and.returnValue("<valid auth token>");
            $state = jasmine.createSpyObj("$state", ["go"]);
            module("angularMoment");
            module("angular-jwt");
            module("ConnectedCampus.Constants");
            module("ConnectedCampus.SocketService");
            module(function ($provide) {
                $provide.value("$cookies", $cookies);
                $provide.value("$state", $state);
                $provide.constant("socialUrl", "Yellow");
            });
            inject(function (_$rootScope_, _$log_, _jwtHelper_, _moment_, _SocketService_) {
                $rootScope = _$rootScope_.$new();
                $log = _$log_;
                jwtHelper = _jwtHelper_;
                moment = _moment_;
                SocketService = _SocketService_;
            })
        });

        describe("Support Functions", function () {


            it("socket.supportFunctions.sortPost should push a new post into the channel's posts array.", function (done) {
                SocketService.data.userChannels = [channel1, channel2, channel3];
                SocketService.supportFunctions.sortPost(postToChannel1);
                expect(SocketService.data.userChannels[0].post_to_channel).toContain(postToChannel1);
                done();
            })
        });
    });
}());

同样,我不确定为什么 这行得通,我不想发表意见,但如果你有类似的问题,这个可能是解决方案。