在 Angular 应用程序中使用 Jasmine 测试 SignalR 时出现问题

Problems with testing SignalR with Jasmine in Angular app

我这里有一个严重的问题。我的应用程序依赖于 SignalR 功能,但因此我无法编写单元测试。我是测试框架的新手,只在简单的情况下使用过 Jasmine。 SignalR 已被证明对我来说挑战太大,但我需要了解如何成功测试它。 这是我的 CommsApp.ts 文件 [typescript]:

/// <reference path="References.ts" />
var commsAnimationsModule = angular.module('forge.communications.animations', ['ngAnimate']);
var commsDirectivesModule = angular.module('forge.communications.directives', []);
var commsServicesModule = angular.module('forge.communications.services', []);
var commsFiltersModule = angular.module('forge.communications.filters', []);

var commsApp = angular.module('forge.communications.CommsApp',
    [
        'ngRoute',
        'ngAnimate',
        'cfValidation',
        'ui.bootstrap',
        'forge.communications.animations',
        'forge.communications.directives',
        'forge.communications.services',
        'forge.communications.filters',
        'angularFileUpload',
        'timeRelative'
    ]);

commsApp.config(function ($routeProvider: ng.route.IRouteProvider, $locationProvider: any) {

        $locationProvider.html5Mode(true);
        $routeProvider.
            when('/scheduled-messages', {
                templateUrl: '/forge/CommsApp/js/Controllers/ScheduledMessageList/ScheduledMessageList.html',
                controller: 'ScheduledMessageListController'
            }).
            when('/geotriggered-messages', {
                templateUrl: '/forge/CommsApp/js/Controllers/GeoMessageList/GeoMessageList.html',
                controller: 'GeoMessageListController'
            }).
            when('/scheduled-message/create', {
                templateUrl: '/forge/CommsApp/js/Controllers/CreateScheduledMessage/CreateScheduledMessage.html',
                controller: 'CreateScheduledMessageController'
            }).
            when('/scheduled-message/edit/:id', {
                templateUrl: '/forge/CommsApp/js/Controllers/EditScheduledMessage/EditScheduledMessage.html',
                controller: 'EditScheduledMessageController'
            }).
            when('/geotriggered-message/create', {
                templateUrl: '/forge/CommsApp/js/Controllers/CreateGeotriggeredMessage/CreateGeotriggeredMessage.html',
                controller: 'CreateGeotriggeredMessageController'
            }).
            when('/geotriggered-message/edit/:id', {
                templateUrl: '/forge/CommsApp/js/Controllers/EditGeotriggeredMessage/EditGeotriggeredMessage.html',
                controller: 'EditGeotriggeredMessageController'
            }).
            otherwise({
                redirectTo: '/scheduled-messages'
            });
    });

commsApp.run(function ($rootScope: ICommsRootScope, commsSignalrEventService: CommsSignalrEventService, commsMgmtHttpService: CommsMgmtHttpServiceClient) {

    // set up the items on the root scope
    $rootScope.SelectedLocale = 'en-ie';
    $rootScope.ForgeApplicationKey = "9496B737-7AE2-4FBD-B271-A64160759177";
    $rootScope.AppVersionString = "1.0.0";
    $rootScope.SessionToken = getCookie("ForgeSessionToken");

    commsSignalrEventService.initialize().done(() => {
        // send any messages about a new user logging in to the application here.
    });

    // call this at app startup to  pre-cache this data for the create and edit pages
    commsMgmtHttpService.GetUpdateMessageEditorOptions();
});

function getCookie(name:string) {
    var value = "; " + document.cookie;
    var parts = value.split("; " + name + "=");
    if (parts.length == 2) return parts.pop().split(";").shift();
}

这是测试文件(我删除了大部分代码,因为它无论如何都不起作用,我一直在这里追逐自己的尾巴):

describe('forge.communications.CommsApp', function () {

    beforeEach(module('forge.communications.CommsApp'));

    var route, rootScope, proxy, commsSignalrEventService;

    beforeEach(inject(function (_$route_, _$rootScope_, _commsSignalrEventService_) {
        route = _$route_,
        rootScope = _$rootScope_;
        commsSignalrEventService = _commsSignalrEventService_;
    }));

    describe("should map routes to controllers and templates", function () {

        it("/scheduled-messages route should be mapped to ScheduledMessageListController", function () {
            expect(2).toEqual(2);
            expect(route.routes['/scheduled-messages'].controller).toBe('ScheduledMessageListController');
        });

    });
});

这是 CommsSignalREventService.ts 文件:

var servicesModule: ng.IModule = angular.module('forge.communications.services');

servicesModule.factory('commsSignalrEventService', function ($rootScope): CommsSignalrEventService {
    return new CommsSignalrEventService($rootScope);
});

class CommsSignalrEventService {

private $rootScope: ng.IScope;
private proxy:any = null;

constructor($rootScope) {
    this.$rootScope = $rootScope;
}

public initialize(): JQueryPromise<any>{

    this.proxy = $.connection['communicationsCenterHub'];

    console.log('proxy',this.proxy);

    //scheduled messages
    this.proxy.client.broadcastScheduledMessageCreatedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-message-created', { messageId: messageId });
    };

    this.proxy.client.broadcastScheduledMessageUpdatedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-message-updated', { messageId: messageId });
    };

    this.proxy.client.broadcastScheduledMessageStateChangedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-message-statechanged', { messageId: messageId });
    };

    this.proxy.client.broadcastScheduledMessageDeletedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-message-deleted', { messageId: messageId });
    };

    //geotriggered messages
    this.proxy.client.broadcastGeoMessageCreatedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-geomessage-created', { messageId: messageId });
    };

    this.proxy.client.broadcastGeoMessageUpdatedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-geomessage-updated', { messageId: messageId });
    };

    this.proxy.client.broadcastGeoMessageStateChangedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-geomessage-statechanged', { messageId: messageId });
    };

    this.proxy.client.broadcastGeoMessageDeletedEvent = (messageId: number) => {
        this.$rootScope.$broadcast('comms-geomessage-deleted', { messageId: messageId });
    };

    var promise = $.connection.hub.start();
    promise.done(function () {
        //console.log('comms signalr hub started');
    });
    return promise;
}

    public RegisterScheduledMessageCreated(messageId: number): void{
        this.proxy.server.registerScheduledMessageCreated(messageId);
    }

    public RegisterScheduledMessageUpdated(messageId: number): void {
        this.proxy.server.registerScheduledMessageUpdated(messageId);
    }

    public RegisterScheduledMessageDeleted(messageId: number): void {
        this.proxy.server.registerScheduledMessageDeleted(messageId);
    }

    public RegisterScheduledMessageStateChanged(messageId: number): void {
        this.proxy.server.registerScheduledMessageStateChanged(messageId);
    }

    public RegisterGeoMessageCreated(messageId: number): void {
        this.proxy.server.registerGeoMessageCreated(messageId);
    }

   public RegisterGeoMessageUpdated(messageId: number): void {
       this.proxy.server.registerGeoMessageUpdated(messageId);
    }

    public RegisterGeoMessageDeleted(messageId: number): void {
        this.proxy.server.registerGeoMessageDeleted(messageId);
    }

    public RegisterGeoMessageStateChanged(messageId: number): void {
        this.proxy.server.registerGeoMessageStateChanged(messageId);
    }
}

每当我 运行 karma 时,我在命令行中经常看到的错误是 forge.communications.CommsApp 遇到声明异常失败 TypeError: Cannot read 属性 'client' of undefined at CommsSignalREventService,这意味着 CommsSignalREventService.ts 文件中的 'proxy' 变量未定义:

this.proxy = $.connection['communicationsCenterHub'];
console.log('proxy', this.proxy); //resolves to UNDEFINED in tests, works fine in the app

this.proxy.client.broadcastScheduledMessageCreatedEvent = function (messageId) {
        _this.$rootScope.$broadcast('comms-message-created', { messageId: messageId });
    };

我将不胜感激任何帮助,因为在这个阶段我试图弄清楚它所花费的时间实在太荒谬了。

我认为您的问题是您的实际应用引用 JavaScript 由 SignalR 在运行时动态生成。此脚本一般位于“~/signalr/hubs”或“~/signalr/js”。

此脚本为您创建 $.connection['communicationsCenterHub'] 代理。如果您的测试中没有引用此脚本,则此代理将为 undefined.

我假设你不是 运行 SignalR 服务器,而你是 运行 Jasmine 测试。如果是这种情况,您有两个选择:

  1. 您只需将 SignalR 在“~/signalr/hubs”处生成的脚本复制到一个文件并在您的测试中引用它。您可以通过将浏览器指向生成的脚本 URL 来手动执行此操作,但是您必须在中心更改时更新此文件。您可以通过 运行 signalr.exe ghp /path:[path to the .dll that contains your Hub class] automate this 作为 post 构建步骤,它将为您生成此文件。

  2. 您完全可以避免使用生成的代理。查看 SignalR JS API reference 的 "Without the generated proxy" 部分。