Angular ui router unit testing (states to urls)

I'm having some trouble unit testing the router in my application, which is built on the Angular ui router. What I want to test is whether state transitions change the URL appropriately (there will be more complicated tests later, but this is where I'm starting.)

Here is the relevant portion of my application code:

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

And the testing code:

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

Similar questions on Stackoverflow (and the official ui router test code) suggest wrapping the $state.go call in $apply should be enough. But I've done that and the state is still not updating. $state.current.name remains empty.

53580 次浏览

Been having this issue as well, and finally figured out how to do it.

Here is a sample state:

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

The unit test below will cover testing the URL w/ params, and executing the resolves which inject its own dependencies:

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

If you want to check only the current state's name it's easier to use $state.transitionTo('splash')

it('should transition to splash', inject(function($state,$rootScope){
$state.transitionTo('splash');
$rootScope.$apply();
expect($state.current.name).toBe('splash');
}));

I realize this is slightly off topic, but I came here from Google looking for a simple way to test a route's template, controller, and URL.

$state.get('stateName')

will give you

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

in your tests.

So your tests could look something like this:

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

For a state that without 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');
});
});
});

NOTE:-

For a nested state we may need to supply more than one template. For ex. if we have a nested state core.public.home and each state, i.e. core, core.public and core.public.home has a templateUrl defined, we will have to add $templateCache.put() for each state's templateUrl key:-

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

Hope this helps. Good Luck.

You could use $state.$current.locals.globals to access all resolved values (see the code snippet).

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

In ui-router 1.0.0 (currently beta) you could try to invoke $resolve.resolve(state, locals).then((resolved) => {}) in the specs. For instance https://github.com/lucassus/angular-webpack-seed/blob/9a5af271439fd447510c0e3e87332959cb0eda0f/src/app/contacts/one/one.state.spec.js#L29

If you're not interested in anything in the content of the template, you could just mock $templateCache:

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