Модульне тестування кутового інтерфейсу користувача (передає 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 (і офіційного тестового коду маршрутизатора інтерфейсу користувача) пропонують обернути виклик $ state.go у $ apply, цього повинно бути достатньо. Але я це зробив, і держава все ще не оновлює. $ state.current.name залишається порожнім.


Гаразд, розібрався (начебто.) Якщо я визначаю макетний маршрутизатор із вбудованими шаблонами замість URL-адрес шаблонів, перехід вдається.
Терренс

Чи можете ви розмістити свій робочий код як відповідь?
Леві Хаквіт з

2
Я поставив це питання майже рік тому. Зараз я думаю, що найкращим способом вирішити цю проблему є використання препроцесора ng-template-to-js у Karma.
Терренс

Більш конкретно: проблема полягає в тому, що якщо завантаження шаблону не вдасться в тесті (тобто через відсутність сервера), зміна стану не відбудеться. Однак, якщо ви не стежите за подією $ stateChangeError, ви не побачите помилки. Тим не менше, оскільки зміна стану не вдається, $ state.current.name не буде оновлено.
Терренс,

Відповіді:


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 замість виклику. очікуйте ($ injector.get ($ state.current.resolve.data)). toBe ('findAll');
Alan Quigley

2
Я дотримувався наведеного вище коду з коригуваннями, andReturnподібними згаданим у вищевказаному коментарі. Однак мій $ state.current.name повертає порожній рядок. Хтось знає чому?
Вікі Леонг,

5
@Philip Я стикаюся з тією ж проблемою $state.current.name- це порожній рядок.
Джой

1
@Joy @VLeong У мене була та сама проблема, а потім зрозуміла, що це пов'язано з утилітою користувальницького інтерфейсу, про яку я пишу, замість цього використовував обіцянки ES6 $q. Все повинно використовувати $qобіцянки, $rootScope.$digest()щоб вирішити всі обіцянки. Мій випадок, мабуть, досить унікальний, але я думав, що поділюсь на всякий випадок.
Stiggler

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
Щоб спростити, я вважаю цю відповідь найбільш прийнятною. Наявність тесту - це приємно і все, але писати тест, який довший, ніж моє ціле визначення користувацького маршруту, просто для тестування однієї кінцевої точки - це просто спосіб неефективний. У будь-якому випадку я голосую за це
Томас

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, тобто core, core.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 (на даний момент бета-версія) ви можете спробувати використати $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>');
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.