Angular ui路由器单元测试(指向网址的状态)


81

我在应用程序中测试路由器时遇到了一些麻烦,该应用程序基于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");
  })
})

关于Stackoverflow的类似问题(以及官方ui路由器测试代码)表明,将$ state.go调用包装在$ apply中就足够了。但是我已经做到了,状态仍然没有更新。$ state.current.name保持为空。


好的,弄清楚了(某种程度上)。如果我定义了一个模拟路由器,使用内联模板而不是模板URL,则转换成功。
Terrence 2013年

您可以发布工作代码作为答案吗?
Levi Hackwith

2
我差不多一年前问过这个问题。我现在的观点是,解决此问题的最佳方法是在Karma中使用ng-template-to-js预处理器
Terrence 2014年

更具体地说:问题是,如果模板下载在测试中失败(即,因为没有服务器),则状态更改将失败。但是,除非您正在监视$ stateChangeError事件,否则不会看到该错误。但是,由于状态更改失败,因此不会更新$ state.current.name。
Terrence

Answers:


125

同样也遇到了这个问题,最终想出了解决方法。

这是一个示例状态:

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');
  });
});

1
很棒的帖子,节省时间,如果您使用字符串定义服务,请使用get而不是invoke。Expect($ injector.get($ state.current.resolve.data))。toBe('findAll');
艾伦·奎格利

2
我按照上面的代码进行了调整,以适应andReturn上面的注释中提到的内容。但是,我的$ state.current.name返回一个空字符串。有人知道为什么吗?
Vicky Leong 2015年

5
@Philip我面临的同样问题$state.current.name是空字符串。
2015年

1
@Joy @VLeong我遇到了同样的问题,然后意识到这是由于我正在编写的ui-router实用程序使用的是ES6 Promise而不是$q。一切都必须使用$q承诺才能$rootScope.$digest()解决所有承诺。我的案子可能很独特,但我想以防万一。
斯蒂格勒2015年

1
@Joy我在$ state.current.name返回空字符串时遇到了同样的问题。我必须将$ rootScope。$ digest()替换为$ httpBackend.flush()。更改之后,我得到了我所期望的。
杰森·布坎南

18

如果您只想检查当前状态的名称,则使用起来更容易 $state.transitionTo('splash')

it('should transition to splash', inject(function($state,$rootScope){
  $state.transitionTo('splash');
  $rootScope.$apply();
  expect($state.current.name).toBe('splash');
}));

4
为了简单起见,我认为这个答案是最可接受的。测试很不错,但是必须编写比我的整个ui-route定义更长的测试,才能测试单个端点,这是效率低下的方法。在任何情况下,我投票这件事
托马斯

15

我意识到这有点不对劲,但是我来自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

1

对于state没有resolve

// 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,即corecore.public并且core.public.home有一个templateUrl定义,我们将不得不增加$templateCache.put()每个状态的templateUrl关键: -

$templateCache.put('app/templates/template1.html'); $templateCache.put('app/templates/template2.html'); $templateCache.put('app/templates/template3.html');

希望这可以帮助。祝好运。


1

您可以$state.$current.locals.globals用来访问所有已解析的值(请参见代码段)。

// 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


1

如果您对模板内容不感兴趣,则可以模拟$ templateCache:

beforeEach(inject(function($templateCache) {
        spyOn($templateCache,'get').and.returnValue('<div></div>');
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.