Angular ui 路由器单元测试(状态到 url)
Posted
技术标签:
【中文标题】Angular ui 路由器单元测试(状态到 url)【英文标题】:Angular ui router unit testing (states to urls) 【发布时间】:2013-12-24 08:53:01 【问题描述】:我在我的应用程序中对路由器进行单元测试时遇到了一些麻烦,该应用程序是基于 Angular ui 路由器构建的。我要测试的是状态转换是否适当地改变了 URL(稍后会有更复杂的测试,但这就是我要开始的地方。)
这是我的应用程序代码的相关部分:
angular.module('scrapbooks')
.config( function($stateProvider)
$stateProvider.state('splash',
url: "/splash/",
templateUrl: "/app/splash/splash.tpl.html",
controller: "SplashCtrl"
)
)
以及测试代码:
it("should change to the splash state", function()
inject(function($state, $rootScope)
$rootScope.$apply(function()
$state.go("splash");
);
expect($state.current.name).to.equal("splash");
)
)
关于 ***(以及官方 ui 路由器测试代码)的类似问题建议将 $state.go 调用包装在 $apply 中就足够了。但是我已经这样做了,状态仍然没有更新。 $state.current.name 保持为空。
【问题讨论】:
好的,想通了(有点)。如果我定义一个模拟路由器,使用内联模板而不是模板 URL,则转换成功。 你能发布你的工作代码作为答案吗? 大约一年前我问过这个问题。我现在的看法是,解决这个问题的最好方法是在 Karma 中使用ng-template-to-js preprocessor。 更具体地说:问题是如果模板下载在测试中失败(即因为没有服务器),状态更改将失败。但是,除非您正在监视 $stateChangeError 事件,否则您不会看到该错误。尽管如此,由于状态改变失败,$state.current.name 不会被更新。 【参考方案1】:也遇到了这个问题,终于知道怎么解决了。
这是一个示例状态:
angular.module('myApp', ['ui.router'])
.config(['$stateProvider', function($stateProvider)
$stateProvider.state('myState',
url: '/state/:id',
templateUrl: 'template.html',
controller: 'MyCtrl',
resolve:
data: ['myService', function(service)
return service.findAll();
]
);
]);
下面的单元测试将涵盖测试带有参数的 URL,并执行注入其自身依赖项的解析:
describe('myApp/myState', function()
var $rootScope, $state, $injector, myServiceMock, state = 'myState';
beforeEach(function()
module('myApp', function($provide)
$provide.value('myService', myServiceMock = );
);
inject(function(_$rootScope_, _$state_, _$injector_, $templateCache)
$rootScope = _$rootScope_;
$state = _$state_;
$injector = _$injector_;
// We need add the template entry into the templateCache if we ever
// specify a templateUrl
$templateCache.put('template.html', '');
)
);
it('should respond to URL', function()
expect($state.href(state, id: 1 )).toEqual('#/state/1');
);
it('should resolve data', function()
myServiceMock.findAll = jasmine.createSpy('findAll').and.returnValue('findAll');
// earlier than jasmine 2.0, replace "and.returnValue" with "andReturn"
$state.go(state);
$rootScope.$digest();
expect($state.current.name).toBe(state);
// Call invoke to inject dependencies and run function
expect($injector.invoke($state.current.resolve.data)).toBe('findAll');
);
);
【讨论】:
很棒的帖子,如果您使用字符串来定义服务,请使用 get 而不是调用来节省时间。期望($injector.get($state.current.resolve.data)).toBe('findAll'); 我按照上面的代码调整了andReturn
,就像上面评论中提到的那样。但是,我的 $state.current.name 返回一个空字符串。有人知道为什么吗?
@Philip 我也面临同样的问题$state.current.name
是空字符串。
@Joy @VLeong 我遇到了同样的问题,然后意识到这是由于我正在编写的 ui-router 实用程序使用的是 ES6 Promise 而不是 $q
。一切都必须使用$q
承诺,以便$rootScope.$digest()
解决所有承诺。我的情况可能非常独特,但我想我会分享以防万一。
@Joy 我遇到了与 $state.current.name 返回空字符串相同的问题。我不得不用 $httpBackend.flush() 替换 $rootScope.$digest()。改变之后,我得到了我的预期。【参考方案2】:
如果您只想检查当前状态的名称,使用$state.transitionTo('splash')
会更容易
it('should transition to splash', inject(function($state,$rootScope)
$state.transitionTo('splash');
$rootScope.$apply();
expect($state.current.name).toBe('splash');
));
【讨论】:
为了简单起见,我发现这个答案是最可以接受的。进行测试很好,但是必须编写一个比我的整个 ui-route 定义更长的测试来测试单个端点只是效率低下的方式。无论如何,我都会投票赞成【参考方案3】:我意识到这有点离题,但我是从 Google 来到这里的,目的是寻找一种简单的方法来测试路由的模板、控制器和 URL。
$state.get('stateName')
会给你
url: '...',
templateUrl: '...',
controller: '...',
name: 'stateName',
resolve:
foo: function ()
在您的测试中。
所以你的测试可能看起来像这样:
var state;
beforeEach(inject(function ($state)
state = $state.get('otherwise');
));
it('matches a wild card', function ()
expect(state.url).toEqual('/path/to/page');
);
it('renders the 404 page', function ()
expect(state.templateUrl).toEqual('views/errors/404.html');
);
it('uses the right controller', function ()
expect(state.controller).toEqual(...);
);
it('resolves the right thing', function ()
expect(state.resolve.foo()).toEqual(...);
);
// etc
【讨论】:
【参考方案4】:对于没有resolve
的state
:
// TEST DESCRIPTION
describe('UI ROUTER', function ()
// TEST SPECIFICATION
it('should go to the state', function ()
module('app');
inject(function ($rootScope, $state, $templateCache)
// When you transition to the state with $state, UI-ROUTER
// will look for the 'templateUrl' mentioned in the state's
// configuration, so supply those templateUrls with templateCache
$templateCache.put('app/templates/someTemplate.html');
// Now GO to the state.
$state.go('someState');
// Run a digest cycle to update the $state object
// you can also run it with $state.$digest();
$state.$apply();
// TEST EXPECTATION
expect($state.current.name)
.toBe('someState');
);
);
);
注意:-
对于嵌套状态,我们可能需要提供多个模板。例如。如果我们有一个嵌套状态core.public.home
并且每个state
,即core
、core.public
和core.public.home
定义了一个templateUrl
,我们将不得不为每个状态的templateUrl
键添加$templateCache.put()
: -
$templateCache.put('app/templates/template1.html');
$templateCache.put('app/templates/template2.html');
$templateCache.put('app/templates/template3.html');
希望这会有所帮助。祝你好运。
【讨论】:
【参考方案5】:您可以使用$state.$current.locals.globals
访问所有已解析的值(参见代码 sn-p)。
// Given
$httpBackend
.expectGET('/api/users/123')
.respond(200, id: 1, email: 'test@email.com');
// When
$state.go('users.show', id: 123 );
$httpBackend.flush();
// Then
var user = $state.$current.locals.globals['user']
expact(user).to.have.property('id', 123);
expact(user).to.have.property('email', 'test@email.com');
在 ui-router 1.0.0(当前为 beta)中,您可以尝试在规范中调用 $resolve.resolve(state, locals).then((resolved) => )
。比如https://github.com/lucassus/angular-webpack-seed/blob/9a5af271439fd447510c0e3e87332959cb0eda0f/src/app/contacts/one/one.state.spec.js#L29
【讨论】:
【参考方案6】:如果你对模板内容不感兴趣,你可以模拟 $templateCache:
beforeEach(inject(function($templateCache)
spyOn($templateCache,'get').and.returnValue('<div></div>');
【讨论】:
以上是关于Angular ui 路由器单元测试(状态到 url)的主要内容,如果未能解决你的问题,请参考以下文章
单元测试时,Angular RC 5 中无法解析路由器的所有参数:(?, ?, ?, ?, ?, ?, ?)
Angular:单元测试路由:预期 '' 是 '/route'