Angular UI Router-Unit-Test (Zustände zu URLs)

81

Ich habe einige Probleme beim Testen des Routers in meiner Anwendung, die auf dem Angular UI-Router basiert. Ich möchte testen, ob Statusübergänge die URL entsprechend ändern (es wird später kompliziertere Tests geben, aber hier fange ich an.)

Hier ist der relevante Teil meines Bewerbungscodes:

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

Und der Testcode:

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

Ähnliche Fragen zu Stackoverflow (und dem offiziellen Testcode des UI-Routers) legen nahe, dass das Aufrufen des Aufrufs $ state.go in $ apply ausreichen sollte. Aber ich habe das getan und der Zustand wird immer noch nicht aktualisiert. $ state.current.name bleibt leer.

Terrence
quelle
Okay, habe es herausgefunden (irgendwie). Wenn ich einen Scheinrouter mit Inline-Vorlagen anstelle von Vorlagen-URLs definiere, ist der Übergang erfolgreich.
Terrence
Können Sie Ihren Arbeitscode als Antwort veröffentlichen?
Levi Hackwith
2
Ich habe diese Frage vor fast einem Jahr gestellt. Meiner Ansicht nach ist der beste Weg, um dieses Problem zu lösen, die Verwendung des Präprozessors ng-template-to-js in Karma.
Terrence
Genauer gesagt: Das Problem besteht darin, dass die Statusänderung fehlschlägt, wenn der Download der Vorlage im Test fehlschlägt (dh weil kein Server vorhanden ist). Wenn Sie jedoch nicht nach dem Ereignis $ stateChangeError suchen, wird der Fehler nicht angezeigt. Da die Statusänderung jedoch fehlschlägt, wird $ state.current.name nicht aktualisiert.
Terrence

Antworten:

125

Ich hatte auch dieses Problem und fand schließlich heraus, wie es geht.

Hier ist ein Beispielstatus:

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

Der folgende Komponententest behandelt das Testen der URL mit Parametern und das Ausführen der Auflösungen, die ihre eigenen Abhängigkeiten einfügen:

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
quelle
1
Toller Beitrag, Zeitersparnis Wenn Sie Zeichenfolgen zum Definieren eines Dienstes verwenden, verwenden Sie get anstelle von invoke. erwarten ($ injizierer.get ($ state.current.resolve.data)). toBe ('findAll');
Alan Quigley
2
Ich folgte dem obigen Code mit Anpassungen, andReturnwie im obigen Kommentar erwähnt. Mein $ state.current.name gibt jedoch eine leere Zeichenfolge zurück. Weiß jemand warum?
Vicky Leong
5
@Philip Ich stehe vor dem gleichen Problem $state.current.nameist leere Zeichenfolge.
Joy
1
@Joy @VLeong Ich hatte das gleiche Problem und stellte dann fest, dass es daran lag, dass das von mir geschriebene UI-Router-Dienstprogramm ES6-Versprechen anstelle von verwendete $q. Alles muss $qVersprechen verwenden $rootScope.$digest(), um alle Versprechen zu lösen. Mein Fall ist wahrscheinlich ziemlich einzigartig, aber ich dachte, ich würde ihn nur für den Fall teilen.
Stiggler
1
@Joy Ich hatte das gleiche Problem mit $ state.current.name, das eine leere Zeichenfolge zurückgab. Ich musste $ rootScope. $ Digest () durch $ httpBackend.flush () ersetzen. Nach dieser Änderung bekam ich, was ich erwartet hatte.
Jason Buchanan
18

Wenn Sie nur den Namen des aktuellen Status überprüfen möchten, ist die Verwendung einfacher $state.transitionTo('splash')

it('should transition to splash', inject(function($state,$rootScope){
  $state.transitionTo('splash');
  $rootScope.$apply();
  expect($state.current.name).toBe('splash');
}));
amatyas001
quelle
4
Um es einfach zu halten, finde ich diese Antwort am akzeptabelsten. Test zu haben ist nett und alles, aber einen Test schreiben zu müssen, der länger ist als meine gesamte UI-Routendefinition, nur um einen einzelnen Endpunkt zu testen, ist einfach zu ineffizient. Auf jeden Fall stimme ich ab
Thomas
15

Mir ist klar, dass dies ein wenig vom Thema abweicht, aber ich bin von Google hierher gekommen, um nach einer einfachen Möglichkeit zu suchen, die Vorlage, den Controller und die URL einer Route zu testen.

$state.get('stateName')

werde dir geben

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

in Ihren Tests.

Ihre Tests könnten also ungefähr so ​​aussehen:

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
quelle
1

Für ein statedas ohne 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');
        });
    });
});

HINWEIS:-

Für einen verschachtelten Status müssen wir möglicherweise mehr als eine Vorlage angeben. Zum Beispiel. wenn wir eine verschachtelte Zustand haben core.public.homeund jeder state, das heißt core, core.publicund core.public.homehat sich zu einem templateUrldefinierten, werden wir hinzufügen müssen $templateCache.put()für jeden Zustand der templateUrlSchlüssel: -

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

Hoffe das hilft. Viel Glück.

Akash
quelle
1

Sie können $state.$current.locals.globalsdamit auf alle aufgelösten Werte zugreifen (siehe Codefragment).

// 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]');

In UI-Router 1.0.0 (derzeit Beta) können Sie versuchen, $resolve.resolve(state, locals).then((resolved) => {})die Spezifikationen aufzurufen . Zum Beispiel https://github.com/lucassus/angular-webpack-seed/blob/9a5af271439fd447510c0e3e87332959cb0eda0f/src/app/contacts/one/one.state.spec.js#L29

Luacassus
quelle
1

Wenn Sie an nichts im Inhalt der Vorlage interessiert sind, können Sie einfach $ templateCache verspotten:

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