2017-10-27 111 views
9

Przeprowadzam migrację do najnowszej stabilnej wersji ui-routera i korzystam z przechodniów cyklu życia $ przejścia, aby wykonać określoną logikę, gdy pewne nazwy stanów są przenoszone na .Jak testować przejściowe haki z ui-routerem 1.0.x

Tak więc w niektórych moich kontrolerów mam ten kinda rzeczy teraz:

this.$transitions.onStart({ }, (transition) => { 
    if (transition.to().name !== 'some-state-name') { 
     //do stuff here... 
    } 
}); 

W moich testów jednostkowych dla kontrolera, wcześniej bym nadawane zdarzenie zmiany stanu na $ rootScope z niektórych nazw państwa jako zdarzenie mówi, że uderzyło w warunki, które musiałem przetestować.

np.

$rootScope.$broadcast('$stateChangeStart', {name: 'other-state'}, {}, {}, {}); 

Od tych wydarzeń państwowych są przestarzałe, co jest poprawny sposób teraz wyzwalać $transitions.onStart(...) haki w testach?

Próbowałem po prostu wywoływać $state.go('some-state-name') w moich testach, ale nigdy nie mogę trafić własną logikę w funkcji wywołania zwrotnego haka przejścia. Według dokumentów here, wywołanie state.go programowo powinno wywołać przejście, chyba że błędnie odczytuję?

Czy ktoś inny zdołał uzyskać testy jednostkowe dla przejściowych haków w swoich kontrolerach pracujących dla nowego routera Ui 1.0.x?

Pełny przykład mojego kodu sterownika za pomocą haka przejściowy:

this.$transitions.onSuccess({ }, (transition) => { 
     this.setOpenItemsForState(transition.to().name); 
    }); 

Test Spec:

describe('stateChangeWatcher', function() { 
     beforeEach(function() { 
     spyOn(vm, 'setOpenItemsForState').and.callThrough(); 
     }); 

     it('should call the setOpenItemsForState method and pass it the state object', function() { 
     $state.go('home'); 
     $rootScope.$apply(); 
     expect(vm.setOpenItemsForState).toHaveBeenCalledWith('home'); 
     }); 
    }); 

Mój szpieg nigdy nie jest trafiony, po uruchomieniu aplikacji lokalnie ten hak ma dostać wywoływany zgodnie z oczekiwaniami, więc musi to być coś, co mam nieprawidłowo skonfigurowane w moich testach. Czy jest coś ekstra, czego potrzebuję, aby przejście zakończyło się sukcesem w teście, ponieważ jestem podłączony do zdarzenia onSuccess?

Dzięki

UPDATE

podniosłem to w pokoju ui-router Gitter i jeden z autorów repo wrócił do mnie sprawdzić połączenia do $state.go('home') w moich testach rzeczywiście biegły sugerując dodając expect($state.current.name).toBe('home'); w mojej specyfikacji testu.

To nie przechodzą na mnie w moim teście, ale nadal jestem w stanie trafić na wezwanie do mojej funkcji w zwrotnego haka przejście:

enter image description here

jestem pewien, jak postępować w tej sprawie, poza instalowaniem polyfill dla starszych zdarzeń $ stateChange, więc mogę użyć mojego poprzedniego kodu, ale wolałbym tego nie robić i wymyślić właściwy sposób testowania haków przejściowych $.

UPDATE 2

następującą odpowiedź estus', mam teraz zgaszone usługę $transitions a także refactored mojego przejścia obsługi hak do prywatnego funkcji wymienionych w moim kontrolera:

export class NavBarController { 
    public static $inject = [ 
    '$mdSidenav', 
    '$scope', 
    '$mdMedia', 
    '$mdComponentRegistry', 
    'navigationService', 
    '$transitions', 
    '$state' 
    ]; 

    public menuSection: Array<InterACT.Interfaces.IMenuItem>; 

    private openSection: InterACT.Interfaces.IMenuItem; 
    private openPage: InterACT.Interfaces.IMenuItem; 

    constructor(
    private $mdSidenav, 
    private $scope, 
    private $mdMedia, 
    private $mdComponentRegistry, 
    private navigationService: NavigationService, 
    private $transitions: any, 
    private $state 
) { 
    this.activate(); 
    } 

    private activate() { 
    this.menuSection = this.navigationService.getNavMenu(); 
    if (this.isScreenMedium()) { 
     this.$mdComponentRegistry.when('left').then(() => { 
     this.$mdSidenav('left').open(); 
     }); 
    } 
    this.setOpenItemsForState(this.$state.$current.name); 
    this.$transitions.onSuccess({ }, this.onTransitionsSuccess); 
    } 

    private onTransitionsSuccess = (transition) => { 
    this.setOpenItemsForState(transition.to().name); 
    } 

    private setOpenItemsForState(stateName: string) { 
     //stuff here... 
    } 
} 

Teraz w moja specyfikacja testu Mam:

describe('Whenever a state transition succeeds', function() { 
     beforeEach(function() { 
     spyOn(vm, 'setOpenItemsForState').and.callThrough(); 
     $state.go('home'); 
     }); 
     it('should call the setOpenItemsForState method passing in the name of the state that has just been transitioned to', function() { 
     expect($transitions.onSuccess).toHaveBeenCalledTimes(1); 
     expect($transitions.onSuccess.calls.mostRecent().args[0]).toEqual({}); 
     expect($transitions.onSuccess.calls.mostRecent().args[1]).toBe(vm.onTransitionsSuccess); 
     }); 
    }); 

Te oczekiwania mijają, ale nadal nie jestem w stanie uderzyć moją wewnętrzną logikę w moim imieniu haka zwrotnego onTransitionsSuccess funkcję zatelefonować do setOpenItemsForState

enter image description here

Co robię źle tutaj?

UPDATE 3

Dzięki ponownie estu byłem zapominając mogę po prostu zadzwonić do mojego nazwie funkcja hak przejście jest odrębny test:

describe('and the function bound to the transition hook callback is invoked', function(){ 
     beforeEach(function(){ 
      spyOn(vm, 'setOpenItemsForState'); 
      vm.onTransitionsSuccess({ 
      to: function(){ 
       return {name: 'another-state'}; 
      } 
      }); 
     }); 
     it('should call setOpenItemsForState', function(){ 
      expect(vm.setOpenItemsForState).toHaveBeenCalledWith('another-state'); 
     }); 
     }); 

A teraz mam 100% populacji :)

enter image description here

Mam nadzieję, że będzie to służyć jako dobry odniesieniu do innych którzy mogą walczyć, aby dowiedzieć się, jak przetestować własne haki przejściowe.

Odpowiedz

2

Dobrą strategią testowania jednostek dla routingu AngularJS jest całkowite zablokowanie routera. Prawdziwy router zapobiega efektywnemu testowaniu urządzeń i zapewnia niepotrzebne ruchome części i nieoczekiwane zachowanie. Ponieważ ngMock zachowuje się inaczej niż w prawdziwej aplikacji, testów takich nie można uznać za odpowiednie testy integracyjne.

Wszystkie używane usługi routera powinny być zgaszone. $stateProvider stub powinien odzwierciedlać jego podstawowy problem, tzn powinno wrócić się na state rozmowy i powinien wrócić $state niedopałek na $get rozmowy:

let mockedStateProvider; 
let mockedState; 
let mockedTransitions; 

beforeEach(module('app')); 

beforeEach(module(($provide) => { 
    mockedState = jasmine.createSpyObj('$state', ['go']); 
    mockedStateProvider = jasmine.createSpyObj('$stateProvider', ['state', '$get']); 
    mockedStateProvider.state.and.returnValue(mockedStateProvider); 
    mockedStateProvider.$get.and.returnValue(mockedState); 
    $provide.provider('$state', function() { return mockedStateProvider }); 
})); 

beforeEach(module(($provide) => { 
    mockedTransitions = jasmine.createSpyObj('$transitions', ['onStart', 'onSuccess']); 
    $provide.value('$transitions', mockedTransitions); 
})); 

Test przyjazny sposób jest zapewnienie związane metody jak wywołania zwrotne zamiast funkcji anonimowych :

this.onTransitionStart = (transition) => { ... }; 
this.$transitions.onStart({ }, this.onTransitionStart); 

Potem zgasił metody mogą być testowane tylko, że nazywano ich z odpowiednimi argumentami:

expect($transitions.onStart).toHaveBeenCalledTimes(1); 
$transitions.onStart.mostRecent().args[0].toEqual({}); 
$transitions.onStart.mostRecent().args[1].toBe(this.onTransitionStart); 

Funkcja wywołania zwrotnego może być testowana bezpośrednio przez wywołanie jej z oczekiwanymi argumentami. Zapewnia to pełne pokrycie, ale pozostawia pewne miejsce na błędy ludzkie, więc testy jednostkowe powinny być poparte testami integracji/e2e z prawdziwym routerem.

+0

Dzięki za odpowiedź i świetne wyjaśnienie - teraz ma to sens. Nie stoję przed moją maszyną, ale jestem podekscytowany, aby spróbować! – mindparse

+0

Podjęłam próbę rozwiązania problemu, ale wciąż mam problemy, gdy próbuję całkowicie zakryć wezwanie mojego przewodnika. Czuję, że brakuje mi czegoś podstawowego, czy możesz pomóc? – mindparse

+0

Na czym polega problem?Nazwij go po prostu spodziewanymi argami, takimi jak 'this.onTransitionStart ({to:() => ({name: ...})})' i sprawdź swoją wewnętrzną logikę, 'expect (ctrl.setOpenItemItemsForState) .toHaveBeenCalledWith (. ..) '. – estus