Prueba de unidad de enrutador de interfaz de usuario angular (estados a URL)

81

Tengo problemas para probar el enrutador en mi aplicación, que se basa en el enrutador de interfaz de usuario angular. Lo que quiero probar es si las transiciones de estado cambian la URL de manera apropiada (habrá pruebas más complicadas más adelante, pero aquí es donde estoy comenzando).

Aquí está la parte relevante de mi código de aplicación:

angular.module('scrapbooks')
 .config( function($stateProvider){
    $stateProvider.state('splash', {
       url: "/splash/",
       templateUrl: "/app/splash/splash.tpl.html",
       controller: "SplashCtrl"
    })
 })

Y el código de prueba:

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

Preguntas similares sobre Stackoverflow (y el código oficial de prueba del enrutador ui) sugieren que envolver la llamada $ state.go en $ apply debería ser suficiente. Pero lo he hecho y el estado todavía no se actualiza. $ state.current.name permanece vacío.

Terrence
fuente
De acuerdo, lo resolví (más o menos). Si defino un enrutador simulado, con plantillas en línea en lugar de URL de plantilla, la transición se realiza correctamente.
Terrence
¿Puedes publicar tu código de trabajo como respuesta?
Levi Hackwith
2
Hice esta pregunta hace casi un año. Mi opinión ahora es que la mejor manera de resolver este problema es usar el preprocesador ng-template-to-js en Karma.
Terrence
Más específicamente: el problema es que si la descarga de la plantilla falla en la prueba (es decir, porque no hay un servidor), el cambio de estado fallará. Sin embargo, a menos que esté atento al evento $ stateChangeError, no verá el error. Sin embargo, debido a que el cambio de estado falla, $ state.current.name no se actualizará.
Terrence

Respuestas:

125

También he tenido este problema y finalmente descubrí cómo hacerlo.

Aquí hay un estado de muestra:

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

La prueba unitaria a continuación cubrirá probar la URL con params y ejecutar las resoluciones que inyectan sus propias dependencias:

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');
  });
});
Philip Chen
fuente
1
Gran publicación, ahorra tiempo si estás usando cadenas para definir un servicio, usa get en lugar de invoke. esperar ($ injector.get ($ state.current.resolve.data)). toBe ('findAll');
Alan Quigley
2
Seguí el código anterior con ajustes andReturncomo se menciona en el comentario anterior. Sin embargo, my $ state.current.name devuelve una cadena vacía. ¿Alguien sabe por qué?
Vicky Leong
5
@Philip Estoy enfrentando el mismo problema $state.current.name: cadena vacía.
Joy
1
@Joy @VLeong Tuve el mismo problema, luego me di cuenta de que se debía a que la utilidad ui-router que estoy escribiendo estaba usando promesas de ES6 en lugar de $q. Todo debe usar $qpromesas para $rootScope.$digest()poder resolver todas las promesas. Mi caso es probablemente bastante único, pero pensé en compartirlo por si acaso.
Stiggler
1
@Joy Estaba teniendo el mismo problema con $ state.current.name devolviendo una cadena vacía. Tuve que reemplazar $ rootScope. $ Digest () con $ httpBackend.flush (). Después de ese cambio, obtuve lo que esperaba.
Jason Buchanan
18

Si desea verificar solo el nombre del estado actual, es más fácil de usar $state.transitionTo('splash')

it('should transition to splash', inject(function($state,$rootScope){
  $state.transitionTo('splash');
  $rootScope.$apply();
  expect($state.current.name).toBe('splash');
}));
amatyas001
fuente
4
Para simplificar, encuentro esta respuesta la más aceptable. Tener una prueba es bueno y todo, pero tener que escribir una prueba que sea más larga que toda mi definición de ruta de interfaz de usuario solo para probar un solo punto final es simplemente ineficiente. En cualquier caso, estoy votando esto
Thomas
15

Me doy cuenta de que esto está un poco fuera de tema, pero vine aquí desde Google buscando una forma sencilla de probar la plantilla, el controlador y la URL de una ruta.

$state.get('stateName')

Te regalaré

{
  url: '...',
  templateUrl: '...',
  controller: '...',
  name: 'stateName',
  resolve: {
    foo: function () {}
  }
}

en tus pruebas.

Entonces, sus pruebas podrían verse así:

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
Weltschmerz
fuente
1

Por un stateeso sin 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');
        });
    });
});

NOTA:-

Para un estado anidado, es posible que necesitemos proporcionar más de una plantilla. Por ej. si tenemos un estado anidado core.public.homey cada uno state, es decir core, core.publicy core.public.hometiene un templateUrldefinido, tendremos que agregar $templateCache.put()para la templateUrlclave de cada estado : -

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

Espero que esto ayude. Buena suerte.

Aakash
fuente
1

Puede utilizar $state.$current.locals.globalspara acceder a todos los valores resueltos (consulte el fragmento de código).

// Given
$httpBackend
  .expectGET('/api/users/123')
  .respond(200, { id: 1, email: '[email protected]');
                                                       
// 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', '[email protected]');

En ui-router 1.0.0 (actualmente beta), puede intentar invocar $resolve.resolve(state, locals).then((resolved) => {})en las especificaciones. Por ejemplo https://github.com/lucassus/angular-webpack-seed/blob/9a5af271439fd447510c0e3e87332959cb0eda0f/src/app/contacts/one/one.state.spec.js#L29

luacassus
fuente
1

Si no está interesado en nada en el contenido de la plantilla, simplemente puede simular $ templateCache:

beforeEach(inject(function($templateCache) {
        spyOn($templateCache,'get').and.returnValue('<div></div>');
}
Jelmer Jellema
fuente