angularjs路由
在构建和交付功能全面的软件的过程中,我们应用了多种技术来检查软件的正确性和质量。 单元测试是这些技术之一。 许多组织都对单元测试给予了极大的关注,因为它降低了发现和修复应用程序潜在问题的成本。
随着我们开始使用成千上万JavaScript行开发应用程序,我们无法逃脱测试代码的麻烦。 几个JavaScript开发人员说,测试JavaScript甚至更为重要,因为直到运行时该语言的行为都是未知的。
幸运的是,AngularJS通过支持诸如依赖注入(DI)之类的功能,使使用该框架编写的代码的测试变得更加容易。 在过去的三篇文章中,我讨论了有关模拟 , 如何测试控制器,服务和提供程序以及如何测试指令的一些技巧 。 本文将介绍测试AngularJS应用程序的Bootstrap块(包括配置块,运行块和路由解析块),作用域事件和动画。
您可以从我们的GitHub存储库中下载本文中使用的代码,您还将在其中找到有关运行测试的说明。
测试配置和运行块
Config和Run块在模块生命周期的开始执行。 它们包含控制模块,小部件或应用程序工作方式的重要逻辑。 测试它们有些棘手,因为它们不能像其他组件一样直接调用。 同时,它们的作用至关重要,因此不可忽视。
考虑以下配置和运行块:
angular.module('configAndRunBlocks', ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider.when('/home', {
templateUrl: 'home.html',
controller: 'HomeController',
resolve: {
bootstrap: ['$q', function ($q) {
return $q.when({
prop: 'value'
});
}]
}
})
.when('/details/:id', {
templateUrl: 'details.html',
controller: 'DetailsController'
})
.otherwise({
redirectTo: '/home'
});
})
.run(function ($rootScope, messenger) {
messenger.send('Bootstrapping application');
$rootScope.$on('$locationChangeStart', function (event, next, current) {
messenger.send('Changing route to ' + next + ' from ' + current);
});
});
与测试提供程序的情况类似,我们需要在测试config和run块中的功能之前确保已加载模块。 因此,我们将使用一个空的注入块来加载模块。
以下代码段模拟了上面代码块中使用的依赖关系并加载了模块:
describe('config and run blocks', function () {
var routeProvider, messenger;
beforeEach(function () {
module('ngRoute');
module(function ($provide, $routeProvider) {
routeProvider = $routeProvider;
spyOn(routeProvider, 'when').andCallThrough();
spyOn(routeProvider, 'otherwise').andCallThrough();
messenger = {
send: jasmine.createSpy('send')
};
$provide.value('messenger', messenger);
});
module('configAndRunBlocks');
});
beforeEach(inject());
});
我故意没有嘲笑$routeProvider
对象,因为我们将在本文后面测试注册的路由。
现在已经加载了模块,配置和运行块已经执行。 因此,我们可以开始测试其行为。 当config块注册路由时,我们可以检查它是否注册了正确的路由。 我们将测试预期的路由数是否已注册。 以下测试验证了config块的功能:
describe('config block tests', function () {
it('should have called registered 2 routes', function () {
//Otherwise internally calls when. So, call count of when has to be 3
expect(routeProvider.when.callCount).toBe(3);
});
it('should have registered a default route', function () {
expect(routeProvider.otherwise).toHaveBeenCalled();
});
});
示例代码中的run块调用服务并注册一个事件。 我们将在本文后面测试该事件。 目前,让我们测试对service方法的调用:
describe('run block tests', function () {
var rootScope;
beforeEach(inject(function ($rootScope) {
rootScope = $rootScope;
}));
it('should send application bootstrap message', function () {
expect(messenger.send).toHaveBeenCalled();
expect(messenger.send).toHaveBeenCalledWith("Bootstrapping application");
});
});
测试范围事件
事件聚合是使两个对象相互交互的好方法之一,即使它们完全不相互了解。 AngularJS通过$scope
上的$scope
$emit
/ $broadcast
事件提供此功能。 应用程序中的任何对象都可以根据需要引发事件或侦听事件。
当应用程序运行时,事件的订阅者和发布者均可用。 但是,由于单元测试是独立编写的,因此单元测试中只有一个可用的对象。 因此,测试规范将必须模仿另一端才能测试功能。
让我们测试一下在上面的运行块中注册的事件:
$rootScope.$on('$locationChangeStart', function (event, next, current) {
messenger.send('Changing route to ' + next + ' from ' + current);
});
每当应用程序的位置更改时, $location
服务都会广播$locationChangeStart
事件。 如前所述,我们需要手动触发此事件并测试消息是否由Messenger发送。 以下测试执行此任务:
it('should handle the $locationChangeStart event', function () {
var next = '/second';
var current = '/first';
rootScope.$broadcast('$locationChangeStart', next, current);
expect(messenger.send).toHaveBeenCalled();
expect(messenger.send).toHaveBeenCalledWith('Changing route to ' + next + ' from ' + current);
});
测试路线
路由定义了用户浏览应用程序的方式。 路由配置中的任何不当或意外更改都会导致不良的用户体验。 因此,路线也应进行测试。
到目前为止, ngRoute和ui-router是AngularJS应用程序中使用最广泛的路由器。 这两个提供程序的路由都必须在config块中定义,而路由数据可通过服务获得。 通过ngRoute配置的路由数据可通过服务$route
。 ui-router的路由数据可通过服务$state
。 这些服务可用于检查是否配置了正确的路由集。
考虑以下配置块:
angular.module('configAndRunBlocks', ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider.when('/home', {
templateUrl: 'home.html',
controller: 'HomeController',
resolve: {
bootstrap: ['$q', function ($q) {
return $q.when({
prop: 'value'
});
}]
}
})
.when('/details/:id', {
templateUrl: 'details.html',
controller: 'DetailsController'
})
.otherwise({
redirectTo: '/home'
});
});
现在让我们测试这些路线。 首先,让我们获取$route
服务的参考:
beforeEach(inject(function ($route) {
route = $route;
}));
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
上面的/home
路由配置了templateUrl
,一个控制器和一个resolve块。 让我们编写断言来测试它们:
it('should have home route with right template, controller and a resolve block', function () {
var homeRoute = route.routes['/home'];
expect(homeRoute).toBeDefined();
expect(homeRoute.controller).toEqual('HomeController');
expect(homeRoute.templateUrl).toEqual('home.html');
expect(homeRoute.resolve.bootstrap).toBeDefined();
});
测试详细路线将是相似的。 我们还具有使用else块配置的默认路由。 默认路由注册为null
作为键值。 以下是对其的测试:
it('should have a default route', function () {
var defaultRoute = route.routes['null'];
expect(defaultRoute).toBeDefined();
});
测试解析块
解析块是在加载路由时创建的工厂,与该路由关联的控制器可以访问它们。 这是一个有趣的场景,因为它们的范围仅限于路由,并且我们仍然需要获取该对象的引用。
我看到测试resolve块的唯一方法是使用$injector
服务调用它。 一旦被调用,就可以像其他任何工厂一样对其进行测试。 以下代码段测试了使用我们在上面创建的本地路由配置的resolve块:
it('should return data on calling the resolve block', function () {
var homeRoute = route.routes['/home'];
var bootstrapResolveBlock = homeRoute.resolve.bootstrap;
httpBackend.expectGET('home.html').respond('<div>This is the homepage!</div>');
var bootstrapSvc = injector.invoke(bootstrapResolveBlock); //[1].call(q);
bootstrapSvc.then(function (data) {
expect(data).toEqual({
prop: 'value'
});
});
rootScope.$digest();
httpBackend.flush();
});
我不得不在上面的测试中模仿templateUrl
,因为在调用摘要循环时AngularJS会尝试移动到默认路由。
同样的方法也可以用来测试$httpInterceptors
。
测试动画
测试动画的技术与测试指令有些相似,但是测试动画更容易,因为动画不像指令那么复杂。
angular-mocks库包含ngAnimateMock
模块,以简化测试动画的工作。 测试动画之前必须先加载此模块。
考虑以下JavaScript动画:
angular.module('animationsApp', ['ngAnimate']).animation('.view-slide-in', function () {
return {
enter: function (element, done) {
element.css({
opacity: 0.5,
position: "relative",
top: "10px",
left: "20px"
})
.animate({
top: 0,
left: 0,
opacity: 1
}, 500, done);
},
leave: function (element, done) {
element.animate({
opacity: 0.5,
top: "10px",
left: "20px"
}, 100, done);
}
};
});
现在让我们编写测试以验证此动画的正确性。 我们需要加载所需的模块并获取所需对象的引用。
beforeEach(function () {
module('ngAnimate', 'ngAnimateMock', 'animationsApp');
inject(function ($animate, $rootScope, $rootElement) {
$animate.enabled(true);
animate = $animate;
rootScope = $rootScope;
rootElement = $rootElement;
divElement = angular.element('<div class="view-slide-in">This is my view</div>');
rootScope.$digest();
});
});
要测试上面定义的动画的enter部分,我们需要以编程方式使元素输入上述代码段中引用的rootElement
。
在测试动画之前要记住的重要一点是,AngularJS会阻止动画运行到第一个摘要循环完成。 这样做是为了使初始绑定更快。 上面代码段中的最后一条语句开始了第一个摘要循环,因此我们不必在每次测试中都这样做。
让我们测试上面定义的输入动画。 它有两个测试用例:
- 输入时,元素应位于顶部10像素和左侧20像素,不透明度0.5
- 输入1秒钟后,元素应位于顶部0px和左侧0px且不透明度为1。 这必须是一个异步测试,因为控件必须在声明前等待1秒钟
以下是以上两种情况的测试:
it('element should start entering from bottom right', function () {
animate.enter(divElement, rootElement);
rootScope.$digest();
expect(divElement.css('opacity')).toEqual('0.5');
expect(divElement.css('position')).toEqual('relative');
expect(divElement.css('top')).toEqual('10px');
expect(divElement.css('left')).toEqual('20px');
});
it('element should be positioned after 1 sec', function (done) {
animate.enter(divElement, rootElement);
rootScope.$digest();
setTimeout(function () {
expect(divElement.css('opacity')).toEqual('1');
expect(divElement.css('position')).toEqual('relative');
expect(divElement.css('top')).toEqual('0px');
expect(divElement.css('left')).toEqual('0px');
done();
}, 1000);
});
同样,对于假动画,我们需要在100毫秒后检查CSS属性的值。 由于测试必须等待动画完成,因此我们需要使测试异步。
it('element should leave by sliding towards bottom right for 100ms', function (done) {
rootElement.append(divElement);
animate.leave(divElement, rootElement);
rootScope.$digest();
setTimeout(function () {
expect(divElement.css('opacity')).toEqual('0.5');
expect(divElement.css('top')).toEqual('10px');
expect(divElement.css('left')).toEqual('20px');
done();
}, 105);
//5 ms delay in the above snippet is to include some time for the digest cycle
});
结论
在本文中,我介绍了过去两年在测试AngularJS代码时学到的大多数测试技巧。 这还不是终点,为真实应用程序的业务场景编写测试时,您会学到更多。 我希望您现在对测试AngularJS代码有足够的了解。 为什么要等? 只需为您到目前为止编写的每一行代码编写测试!
翻译自: https://www.sitepoint.com/angularjs-testing-tips-bootstrap-blocks-routes-events-animations/
angularjs路由