Como testar ganchos de transição com o ui-router 1.0.x

11

Estou migrando para a versão estável mais recente do ui-router e estou usando os ganchos do ciclo de vida de $ transições para executar determinada lógica quando determinados nomes de estado estão sendo transferidos para.

Então, em alguns dos meus controladores eu tenho esse tipo de coisa agora:

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

Em meus testes de unidade para o controlador, anteriormente eu transmitiria um evento de mudança de estado no $ rootScope com os nomes de determinados estados como os argumentos do evento para atingir as condições que eu precisava testar.

por exemplo,

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

Como esses eventos de estado estão obsoletos, qual é a maneira correta de acionar os $transitions.onStart(...) dos ganchos nos testes?

Eu tentei apenas chamar $state.go('some-state-name') em meus testes, mas nunca consigo atingir minha própria lógica dentro da função de retorno de chamada do gancho de transição. De acordo com os documentos aqui , chamar state.go programaticamente deve desencadear uma transição, a menos que eu esteja interpretando mal ?

Alguém mais conseguiu obter testes de unidade para ganchos de transição em seus controladores que trabalham para o novo ui-router 1.0.x?

Exemplo completo do código do meu controlador usando um gancho de transição:

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

especificação de teste:

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

Meu espião nunca é atingido, ao executar o aplicativo localmente, esse gancho é invocado como esperado, então deve ser algo que eu configurei incorretamente em meus testes. Existe algo extra que eu preciso para fazer a transição ter sucesso no teste, já que estou conectando o evento onSuccess?

Obrigado

UPDATE

Eu levantei isso na sala do ui-roteador no gitter e um dos colaboradores do repositório me retornou sugerindo que eu checasse a chamada para $state.go('home') em meus testes, na verdade correu adicionando expect($state.current.name).toBe('home'); na minha especificação de teste.

Isso passa para mim no meu teste, mas ainda não consigo acessar a chamada para minha função no retorno de chamada do gancho de transição:

Nãoseiaocertocomoproceder,alémdeinstalaro polyfill para os eventos $ stateChange legados para que eu possa usar meu código anterior, mas prefiro não fazer isso e descobrir a maneira correta de testar os ganchos $ transition.

UPDATE 2

Seguindo a resposta da estus, agora retirei o serviço $transitions e também refatorizei meu manipulador de gancho de transição em uma função nomeada privada no meu controlador:

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...
    }
}

Agora, na minha especificação de teste, tenho:

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

Essas expectativas passam, mas ainda não consigo atingir minha lógica interna em minha função callback% hook_back% chamada que faz uma chamada para onTransitionsSuccess

Oqueestoufazendodeerradoaqui?

UPDATE3

Maisumavezobrigadoaestu,euestavaesquecendoqueeupossosimplesmentechamarminhafunçãodeganchodetransiçãonomeadaéumtesteseparado:

describe('andthefunctionboundtothetransitionhookcallbackisinvoked',function(){beforeEach(function(){spyOn(vm,'setOpenItemsForState');vm.onTransitionsSuccess({to:function(){return{name:'another-state'};}});});it('shouldcallsetOpenItemsForState',function(){expect(vm.setOpenItemsForState).toHaveBeenCalledWith('another-state');});});

Eagorarecebo100%decobertura:)

Espero que isso sirva como uma boa referência para outras pessoas que podem estar se esforçando para descobrir como testar seus ganchos de transição.

    
por mindparse 27.10.2017 в 14:55
fonte

1 resposta

5

Uma boa estratégia de teste de unidade para o roteamento do AngularJS é o stub de um roteador completamente. O roteador real impede que as unidades sejam testadas com eficiência e fornece peças móveis desnecessárias e comportamento inesperado. Como o ngMock se comporta de maneira diferente da aplicação real, esses testes também não podem ser considerados testes de integração.

Todos os serviços de roteador em uso devem ser stubbed. $stateProvider stub deve refletir seu comportamento básico, ou seja, deve retornar a si mesmo em state call e deve retornar $state stub em $get call:

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

Uma maneira fácil de testar é fornecer métodos ligados como retornos de chamada em vez de funções anônimas:

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

Então, os métodos stubbed podem ser testados apenas para serem chamados com argumentos apropriados:

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

Uma função de retorno de chamada pode ser testada diretamente chamando-a com argumentos esperados. Isso fornece cobertura completa, mas deixa algum lugar para erro humano, portanto, os testes de unidade devem ter backup com testes de integração / e2e com roteador real.

    
por estus 12.11.2017 / 20:00
fonte