在 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 测试。如果是这种情况,您有两个选择:
您只需将 SignalR 在“~/signalr/hubs”处生成的脚本复制到一个文件并在您的测试中引用它。您可以通过将浏览器指向生成的脚本 URL 来手动执行此操作,但是您必须在中心更改时更新此文件。您可以通过 运行 signalr.exe ghp /path:[path to the .dll that contains your Hub class]
automate this 作为 post 构建步骤,它将为您生成此文件。
您完全可以避免使用生成的代理。查看 SignalR JS API reference 的 "Without the generated proxy" 部分。
我这里有一个严重的问题。我的应用程序依赖于 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 测试。如果是这种情况,您有两个选择:
您只需将 SignalR 在“~/signalr/hubs”处生成的脚本复制到一个文件并在您的测试中引用它。您可以通过将浏览器指向生成的脚本 URL 来手动执行此操作,但是您必须在中心更改时更新此文件。您可以通过 运行
signalr.exe ghp /path:[path to the .dll that contains your Hub class]
automate this 作为 post 构建步骤,它将为您生成此文件。您完全可以避免使用生成的代理。查看 SignalR JS API reference 的 "Without the generated proxy" 部分。