Angular UI 라우터 단위 테스트 (URL 상태)


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 라우터 테스트 코드)에 대한 유사한 질문은 $ apply에서 $ state.go 호출을 래핑하는 것으로 충분할 것이라고 제안합니다. 그러나 나는 그것을했고 상태는 여전히 업데이트되지 않습니다. $ state.current.name은 비어 있습니다.


좋아, 알아 냈어 (일종) 템플릿 URL 대신 인라인 템플릿을 사용하여 모의 라우터를 정의하면 전환이 성공합니다.
Terrence

작업 코드를 답변으로 게시 할 수 있습니까?
Levi Hackwith

2
나는 거의 1 년 전에이 질문을했다. 이제이 문제를 해결하는 가장 좋은 방법 은 Karma에서 ng-template-to-js 전처리기를 사용하는 것 입니다.
Terrence 2014

더 구체적으로 말하자면 문제는 테스트에서 템플릿 다운로드가 실패하면 (즉, 서버가 없기 때문에) 상태 변경이 실패한다는 것입니다. 그러나 $ stateChangeError 이벤트를 관찰하지 않는 한 오류가 표시되지 않습니다. 그럼에도 불구하고 상태 변경이 실패하기 때문에 $ state.current.name은 업데이트되지 않습니다.
Terrence

답변:


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
서비스를 정의하기 위해 문자열을 사용하는 경우 시간을 절약 할 수있는 훌륭한 게시물입니다. invoke 대신 get을 사용하세요. expect ($ injector.get ($ state.current.resolve.data)). toBe ( 'findAll');
Alan Quigley

2
andReturn위의 주석에서 언급 한 것과 같이 조정하여 위의 코드를 따랐습니다 . 그러나 내 $ state.current.name은 빈 문자열을 반환합니다. 이유를 아는 사람 있나요?
Vicky Leong

5
@Philip 나는 같은 문제에 직면하고 있는데 $state.current.name빈 문자열입니다.
조이

1
@Joy @VLeong 나는이 같은 문제가 있었는데 내가 쓰는 ui-router 유틸리티로 인해 $q. 모든 $q약속 $rootScope.$digest()을 해결 하려면 모든 것이 약속을 사용해야합니다 . 제 경우는 아마도 매우 독특하지만 만일을 대비하여 공유 할 것이라고 생각했습니다.
Stiggler 2015-07-27

1
@Joy 빈 문자열을 반환하는 $ state.current.name과 동일한 문제가 발생했습니다. $ rootScope. $ digest ()를 $ httpBackend.flush ()로 바꿔야했습니다. 그 변화에 따라 기대했던 것을 얻었습니다.
Jason Buchanan

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 정의보다 긴 테스트를 작성해야하는 것은 비효율적 인 방법입니다. 어쨌든 나는 이것을 투표하고있다
Thomas

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

A에 대한 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예를 core, core.public그리고 core.public.homeA가있다 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 (현재 베타) $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>');
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.