So i've got my code:
(function (ng) {
ng.module('myModuleName')
.provider('myProviderName', ['importedThing', function (importedThing) {
//lots of cool stuff happens in here
}];
}])
.config(['$stateProvider', '$urlRouterProvider', 'importedThing', 'myProviderNameProvider', function ($stateProvider, $urlRouterProvider, importedThing, myProvider) {
window.stateProvider = $stateProvider;
$urlRouterProvider.otherwise(function ($injector, $location) {
$location.path("/pages");
});
}]);
}(angular));
I need to unit test this line for code coverage:
$location.path("/" + myProvider.coolString);
But I can't figure out how. I've seen several people do it with controllers, but not with providers.
I've got the unit test set up like:
beforeEach(function () {
angular.module('dsLocale', function () { }).config(function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
$urp = $urlRouterProvider;
$urp.deferIntercept();
});
});
//Lines are commented to represent random stuff I've tried already.
it('should call otherwise', function () {
//$urp.otherwise('/otherwise');
//$location.path("/lastrule");
console.log($location.path());
//local.$emit("$locationChangeSuccess");
expect(true).toBe(true);
});
I feel like it should be as easy as just changing $location.Path to something that doesn't exist, so UIRouter can do it's thing but it doesn't appear to be that easy. Any help is appreciated, and thanks in advance!
just had the same problem. I wanted to test the otherwise functionality, but did not how, i figured out a way and this worked for me.
I am setting the path with location.path, firing the $locationChangeSuccess event and then it works
it('redirects to otherwise page after locationChangeSuccess', function() {
location.path('/nonExistentPath');
rootScope.$emit("$locationChangeSuccess");
expect(location.path()).toBe("/");
});
without the event, the path is the of course /nonExistentPath
--
this is my full test file. Be aware, i am using browserify and the file maybe looks a little different than yours
'use strict';
require('../../../app');
var objectToTest = 'CoreRouterConfig';
describe(objectToTest, function () {
var rootScope,
state,
location;
beforeEach(angular.mock.module('ui.router'));
beforeEach(angular.mock.module('core'));
beforeEach(inject(function ($rootScope, $state, $location) {
rootScope = $rootScope;
state = $state;
location = $location;
}));
it('should respond home state', function() {
expect(state.href('home')).toEqual('#/');
});
it('init path is correct', function() {
rootScope.$emit("$locationChangeSuccess");
expect(location.path()).toBe("/");
});
it('redirects to otherwise page after locationChangeSuccess', function() {
location.path('/nonExistentPath');
rootScope.$emit("$locationChangeSuccess");
expect(location.path()).toBe("/");
});
it('does not redirect to otherwise page without locationChangeSuccess', function() {
location.path('/nonExistentPath');
expect(location.path()).toBe("/nonExistentPath");
});
});
Related
I have an Angular framework I am building that I am trying to make router agnostic, I would like to be able to use any router. So far I have tested it with ng router and it works fine, but when I try to use UI-Router I am getting the injector error. I am not sure if I have placed it in the wrong module, or if it a deeper issue. The framework broadcasts a route or a state, depending on how I set up the Framework directive. I have injected UI-Router into my main module and injected $stateProvider into my controller that needs to use it. I am stumped. Any help would be greatly appreciated.
Here is main module:
(function () {
"use strict";
angular.module("app", ["ptFramework", "ui.router", "ngStorage", "ui.bootstrap"]);
})();
Here is framework module:
(function () {
"use strict";
angular.module("ptFramework", [,"ptMenu", "ptDashboard"]);
})();
Here is framework controller:
(function () {
"use strict";
angular.module("ptFramework").controller("ptFrameworkController",
['$scope', '$window', '$timeout', '$rootScope', '$stateProvider',
function ($scope, $window, $timeout, $rootScope, $stateProvider) {
$scope.isMenuVisible = true;
$scope.isMenuButtonVisible = true;
$scope.isMenuVertical = true;
$scope.$on('pt-menu-item-selected-event', function (evt, data) {
$scope.stateString = data.state;
$stateProvider.go(data.state);
checkWidth();
broadcastMenuState();
});
$scope.$on('pt-menu-orientation-changed-event', function (evt, data) {
$scope.isMenuVertical = data.isMenuVertical;
$timeout(function () {
$($window).trigger('resize');
}, 0);
});
$($window).on('resize.ptFramework', function () {
$scope.$apply(function () {
checkWidth();
broadcastMenuState();
});
});
$scope.$on("$destroy", function () {
$($window).off("resize.ptFramework"); // remove the handler added earlier
});
var checkWidth = function () {
var width = Math.max($($window).width(), $window.innerWidth);
$scope.isMenuVisible = (width >= 768);
$scope.isMenuButtonVisible = !$scope.isMenuVisible;
};
$scope.menuButtonClicked = function () {
$scope.isMenuVisible = !$scope.isMenuVisible;
broadcastMenuState();
};
var broadcastMenuState = function () {
$rootScope.$broadcast('pt-menu-show',
{
show: $scope.isMenuVisible,
isVertical: $scope.isMenuVertical,
allowHorizontalToggle: !$scope.isMenuButtonVisible
});
};
$timeout(function () {
checkWidth();
}, 0);
}
]);
})();
As you can see I have injected $stateProvider in both the minsafe array and the function. I dont understand why I am getting this error
Here is the rote.config where I use it:
"use strict";
angular.module('app').config([
'$stateProvider', function ($stateProvider) {
$stateProvider
.state('dashboard',
{
url: "/dashboard",
template: "<h1>dashboard</h1>"
});
Any input would be greatly appreciated.
Thanks,
John.
In the controller:
$stateProvider.go(data.state);
should be
$state.go(data.state);
As a consequence, inject $state instead of $stateProvider.
I have found many articles here how to test Angular's config phase and I was able to create my tests against restangular and LocalStorageModule module configuration. The only one I cannot solve yet is checking whether the interceptor was added or not. I do not need to test the service because it is a 3rd party stuff, I consider it is already tested - hopefully.
The question is that, how can I spy on $httpProvider.interceptors.push method which is called in configuration phase?
Thanks for any help in advance!
Here is my code:
(function () {
'use strict';
angular.module('myapp', [
// Angular modules
'ngAnimate',
'ngRoute',
// Custom modules
'myapp.layout',
// 3rd Party Modules
'LocalStorageModule',
'http-auth-interceptor',
'restangular'
])
.config(function (RestangularProvider) {
RestangularProvider.setBaseUrl('http://.../services/webapi/');
})
.config(function (localStorageServiceProvider) {
localStorageServiceProvider.setPrefix('myapp');
})
.config(function($httpProvider) {
$httpProvider.interceptors.push('authInterceptorFactory');
});
})();
'use strict';
describe('myapp configuration', function() {
var RestangularProvider,
localStorageServiceProvider,
$httpProvider;
//modules
beforeEach(function () {
angular.module('myapp.layout', []);
angular.module('http-auth-interceptor', []);
});
//providers
beforeEach(function () {
module('restangular', function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(RestangularProvider, 'setBaseUrl').and.callThrough();
});
module('LocalStorageModule', function (_localStorageServiceProvider_) {
localStorageServiceProvider = _localStorageServiceProvider_;
spyOn(localStorageServiceProvider, 'setPrefix').and.callThrough();
});
module('myapp', function(_$httpProvider_) {
$httpProvider = _$httpProvider_;
spyOn($httpProvider.interceptors, 'push').and.callThrough();
});
//module('myapp');
inject();
});
describe('Restangular configuration', function() {
it('setBaseUrl is set up', function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
describe('localStorage configuration', function() {
it('setPrefix is set up', function () {
expect(localStorageServiceProvider.setPrefix).toHaveBeenCalled();
});
});
describe('$httpProvider configuration', function() {
it('an interceptor is added', function() {
expect($httpProvider.interceptors.push).toHaveBeenCalled();
});
});
});
I've just been doing this myself and its actually surprisingly easy. Below are two ways you can do this, the first is the way I would recommend.
The thing to keep in mind is when you initialise a module the config part will be run automatically, you can use this to either test directly or help set up a test.
Option 1 - Using a fake module to setup
describe('config sets $httpProvider interceptor', function () {
var $httpProvider;
beforeEach(function () {
// First we initialise a random module, we will get $httpProvider
// from it and then use that to spyOn.
module(function (_$httpProvider_) {
$httpProvider = _$httpProvider_;
spyOn($httpProvider.interceptors, 'push');
});
// Now we initialise the app we want to test, $httpProvider will be
// the spy from before.
module('myapp');
inject();
});
it('should add to $httpProvider interceptors', function () {
expect($httpProvider.interceptors.push)
.toHaveBeenCalledWith('authInterceptorFactory');
});
});
Option 2 - Using just your module
describe('config sets $httpProvider interceptor', function () {
var $httpProvider;
beforeEach(function () {
// First we initialise a your module, we will get $httpProvider
// from it and then use that to assert on.
module('myapp', function (_$httpProvider_) {
$httpProvider = _$httpProvider_;
});
inject();
});
it('should add to $httpProvider interceptors', function () {
expect($httpProvider.interceptors).toEqual(['authInterceptorFactory']);
});
});
Again, my recommendation (and the way I did it) is with option 1.
I'm new at unit testing of Angular with Jasmine, I have been struggling with this code for a few hours, I went through a lot articles and answers here, ,but, I can't find the solution.
The services are mocks as you can see. The problem is that, the code below throws that error it does not find the authenticationService variable. However, based on the info I collected today, it should be injected into the "it" method.
If I rewrite the code and the necessary stuff is injected at the "it" method than it is works. But, it is ugly and not the way which should be followed, because it causes boilerplate code.
What I'm doing wrong?
I tried to move the inject(function ($rootScope, $controller) block to over creating the mocks but it didn't help, and later I understood that there is a proper order in injecting should be kept in mind. So I put them back in the nested describe block.
The purpose of the test is check whether the authenticationService.authenticate() is called when the event:auth-loginRequired is fired. I'm not sure whether the assert part of the code is correct. I'm going to work on it once the issue I described above is fixed.
describe('dilibShell module speccifications', function ()
{
var $location, $rootScope, scope, AuthenticationService, AuthService, Common, ShellController;
beforeEach(module('dilibShell'));
beforeEach(function ()
{
AuthenticationService = {
authenticate: function (user)
{
return 'user';
}
}
AuthService = {
loginConfirmed: function () { }
}
Common = {
activateController: function () { },
logger: {
getLogFn: function () { }
}
}
module(function ($provide)
{
$provide.value('authenticationService', AuthenticationService);
});
module(function ($provide)
{
$provide.value('authService', AuthService);
});
module(function ($provide)
{
$provide.value('common', Common);
});
});
describe('shellController Specification', function ()
{ beforeEach(function ()
{
inject(function ($rootScope, $controller)
{
rootScope = $rootScope;
scope = $rootScope.$new();
ShellController = $controller('shellController', {
$scope: scope
});
});
});
it('When event:auth-loginRequired is caught then authenticationService.authenticate must be invoked.',
(function ()
{
expect(authenticationService.authenticate()).toBe('user');
//arrange
//spyOn(authenticationService, 'authenticate');
scope.$digest();
//act
rootScope.$broadcast('event:auth-loginRequired');
//assert
expect(authenticationService.authenticate).toHaveBeenCalledWith();
}));
});
});
Update:
Based on the answers here I modified my code and the it block looks like below and it is working fine, not counting another error in the code, but that one is over this scope.
So, my question is that it is inevitable that, in case of injecting services mocks, I have to call inject in the particular it blocks?
it('When event:auth-loginRequired is caught then authenticationService.authenticate must be invoked.',
(inject(function (authenticationService)
{
expect(authenticationService.authenticate()).toBe('user');
//arrange
spyOn(authenticationService, 'authenticate');
//scope.$digest();
//act
rootScope.$broadcast('event:auth-loginRequired');
//assert
expect(authenticationService.authenticate).toHaveBeenCalledWith();
})));
In my AngularJS app I have a service that is based on WebSocket. If the WebSocket connection breaks, I want to fully 'restart' the application (go to default page from $route, recreate the service etc.). Is that achievable? This is how I started, but from that point I have no idea how to proceed:
Module:
(function () {
angular.module('mainModule', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'partials/home.html',
controller: 'appController'
}).
when('/register', {
templateUrl: 'partials/register.html',
controller: 'appController'
}).
otherwise({
redirectTo: '/home'
});
}]);
}());
Service:
(function () {
var app = angular.module('mainModule');
app.service('$wsService', ['$q', '$window', function ($q, $window) {
$window.console.log("WebSocket SERVICE: started");
var self = this;
var ws = new WebSocket("ws://127.0.0.1:9090");
this.isConnected = function (m) {};
ws.onopen = function () {
$window.console.log("WebSocket SERVICE: connected");
self.isConnected(true);
};
ws.onmessage = function (m) {
//do whatever I want to do
};
ws.onerror = function () {
$window.console.log("WebSocket SERVICE: disconnected");
self.isConnected(false);
};
}]);
}());
Controller:
(function () {
var app = angular.module('mainModule');
app.controller('appController', ['$route', '$scope', '$wsService', function ($route, $scope, $wsService) {
$wsService.isConnected = function (m) {
//restart logic
};
}]);
}());
So as my 'restart logic' I tried "$route.reload();" but as you already know it doesn't do what I need. Eventually I will have a warning message pop up (bootstrap modal) informing the user that the connection has been lost, and on a button click in that modal it will reload the app and go to /home. I am not asking how to do that popup etc as this is already done. As for now however, I need to figure out just the logic for total reload of the app. Any ideas? Thanks.
To answer my own question, achieved with a trial and error:
$scope.$apply(function() {
$location.path('/home');
$window.location.reload();
});
This will go to /home (default) and reload everything, thus creating new service, module, controllers etc. If there is a better way of doing it (if I change default path to /blah in my module, this won't pick it up and thus I will have to edit this code too), let me know :)
I achieved the same thing doing:
$window.location.href = '/home';
A little tweak I did to your answer that helped a lot with the UI refreshing. Is to do the path change inside the reload success callback:
$window.location.reload().then(
function success(){
$location.path('/home');
},
function error(error) {}
);
Most of the time it gives a very smooth transition, presuming you are restarting while redirecting to a different page.
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.
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>');
}