Missing compiled element contents when unit testing ngView - angularjs

After reading the angular ngViewSpec.js file, I've finally figured how to test my controller with routes, controllerAs and forms. Here is my unit test:
describe("mainControllerSpec", function() {
var element;
beforeEach(module("nisArch"));
beforeEach(module("ngRoute"));
it("clickSearch should change location with limitResults true", function () {
var path, search;
var ctrl;
module(function () {
return function ($rootScope, $compile) {
element = $compile("<div ng-view></div>")($rootScope);
};
});
module(function ($compileProvider, $routeProvider) {
$routeProvider.when("/", {
title: "Home",
controller: getController,
controllerAs: "vm",
templateUrl: "templates/mainView.html"
});
});
inject(function ($location, $controller, $rootScope, $route, $templateCache) {
ctrl = $controller("mainController", { $location: $location });
$location.path("/");
$rootScope.$digest();
//breakpoint here. element.html() == "".
//Expected contents of $templateCache.get(...)
ctrl.queryString = "AS65402";
ctrl.clickSearch();
path = $location.path();
search = $location.search();
expect(path).toEqual("/Search");
expect(search).toEqual({ q: "AS65402", limitResults: "true" });
});
function getController() {
return ctrl;
}
});
});
If I understand correctly what's going on here:
I create an element with my ng-view directive, compile it, bind it to the rootscope. I then create the route and controller. Finally I set the $location and do the main part of my unit test.
This is working fine and my test is passing. What's puzzling me is that I can't see the contents of element. I would have expected them to be the contents of templates/mainView.html after the router change and $digest(). But I get nothing. I injected $route to check $route.current.templateHtml and I injected $templateCache to check that. Both correct.
Should I not expect to find the contents using element.html()?
Or have I completely misunderstood what's going on here?
Thanks for any help or insight,
Marcus

It turns out that the magic is in element.siblings(). I got the first clue with element[0]:
<!-- ngView: -->
Which explains why element.html() and element.text() were undefined
Everything I was looking for is under element.siblings()[0]:
<div class="ng-scope" ng-view="">...</div>
I found the element[0] trick from:
https://learn.jquery.com/using-jquery-core/faq/how-do-i-pull-a-native-dom-element-from-a-jquery-object/
Hope this helps someone,
Marcus

In my case I had to use ngView[0].nextSibling here is my test
it 'check view content', ->
inject ($route, $location, $rootScope, $httpBackend, $compile)->
ngView = $compile("<div ng-view></div>")($rootScope)
using $httpBackend, ->
#.expectGET(url_Template).respond(html)
#.expectGET(url_Data ).respond test_Data
$location.path(url)
$httpBackend.flush()
element = ngView[0].nextSibling
$scope = angular.element(element).scope()
$scope.team.assert_Is 'team-A'
$scope.table.assert_Is metadata: 42

Related

Unit-testing secondary routing controller

Currently i'm developing a simple pizza-webshop for a school assignment. I'm experienced in Java & C# but AngularJS is new to me.
I created a single page application with 1 main order controller (index contains an overview of the total order) and another menu controller to load the .JSON menu into a partial view.
The order controller is set in the index as follows;
index.html
<html ng-app="pizzaApp" ng-controller="orderCtrl">
<head>
<link rel="stylesheet" href="CSS/style.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-route.min.js"></script>
<script src="Apps/pizzaApp.js"></script>
<script src="Controllers/menuCtrl.js"></script>
<script src="Controllers/orderCtrl.js"></script>
</head>
and the menu controller is set in the app's routing as follows;
pizzaApp.js
var app = angular.module("pizzaApp", ["ngRoute"]);
// View routing
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'Views/main.html'
})
.when('/pizzas', {
templateUrl: 'Views/pizzas.html',
controller: 'menuCtrl'
})
.when('/pizzas/:param', {
templateUrl: 'Views/pizzaInfo.html',
controller: 'menuCtrl'
})
.when('/orders', {
templateUrl: 'Views/orders.html',
})
});
I managed to write a working (and passing) unit-test for the order controller as follows;
orderCtrl.spec.js
describe('Controllers', function() {
beforeEach(module('pizzaApp'));
var $controller;
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('orderCtrl Test', function() {
var $scope, controller;
beforeEach(function() {
$scope = {};
controller = $controller('orderCtrl', { $scope: $scope });
});
it('orderCtrl.addPizza methode', function() {
//code...
});
it('orderCtrl.clearList methode', function() {
//code...
});
});
});
However, if i try to write a simple unit test for the menu controller in the exact same way, it says the scope/controller is undefined and/or gives me back nulls resulting in the following error message;
Expected null to equal [ ].
at UserContext.<anonymous> (C:/Users/Ken/Git repositories/angular-phonecat/app/practicum/Unit_tests/menuCtrl.spec.js:88:34)
Error: Unexpected request: GET Models/menu.json
Expected GET ../Models/menu.json
menuCtrl.spec.js
describe('menuCtrl Tests', function() {
beforeEach(module('pizzaApp'));
var $controller, $httpBackend;
beforeEach(inject(function(_$controller_, _$httpBackend_){
$controller = _$controller_;
$httpBackend = _$httpBackend_;
$httpBackend.expectGET("../Models/menu.json").respond([{name: 'Margaritha'}, {name: 'Shoarma'}, {name: 'Supreme'}]);
}));
describe('menuCtrl Tests', function() {
var $scope, controller;
beforeEach(function() {
$scope = {};
controller = $controller('menuCtrl', { $scope: $scope});
});
it('should create a `menu` property with 3 pizzas with `$httpBackend`', function() {
jasmine.addCustomEqualityTester(angular.equals);
expect($scope.Menu).toEqual([]);
$httpBackend.flush();
expect($scope.Menu).toEqual([{name: 'Margaritha'}, {name: 'Shoarma'}, {name: 'Supreme'}]);
});
});
});
menuCtrl.js
app.controller('menuCtrl', function ($scope, $http, $routeParams) {
$scope.param = $routeParams.param; //URL Parameter (.../index.html#!/pizzas/#)
$scope.test="123";
//Menu array inladen vanuit .JSON
$http.get('Models/menu.json')
.then(function (menu) {
$scope.Menu = menu.data;
});
});
I have read the f*cking manual, searched google for many-many hours, read all similar questions on stack and i have been trying to solve this for about 2 weeks total, all to no avail...
I have tried changing the unit tests, controllers, front-end, routing, dependency injections, using controller instead of scope, etc. etc. ... and i am COMPLETELY stuck!
In menuCtrl.js, add:
$scope.Menu = [];
before the GET request, i.e. after
$scope.test="123";
The test is expecting a scope variable Menu to exist and since it is not initialized until after the GET request, the test is not passing.
Also a side note,
In the test, you are expecting a GET request to the URI "../Models/menu.json" (in $httpBackend.expectGET()) and making the GET request to the URI "Models/menu.json". If this is unintentional, you may need to change either of those values

jasmine mocking service inside service

I'm testing a directive ('planListing') that has a dependency on a service called 'planListingService'. This service has a dependency to another service called 'ajax' (don't shoot the messenger for the bad names).
I'm able to compile the directive, load its scope and get the controller WITH A CAVEAT. As of now I am being forced to mock both services 'planListingService' and 'ajax' otherwise I will get an error like this:
Error: [$injector:unpr] Unknown provider: ajaxProvider <- ajax <- planListingService
http://errors.angularjs.org/1.3.20/$injector/unpr?p0=ajaxProvider%20%3C-%20ajax%20%3C-%20planListingService
I thought that because I was mocking up the 'planListingService' that I wouldn't have to actually bother with any implementation nor any dependencies of this service. Am I expecting too much?
Here is the code in a nutshell:
planListing.js
angular.module('myApp')
.directive('planListing', planListing)
.controller('planListingCtrl', PlanListingCtrl);
function planListing() {
var varDirective = {
restrict: 'E',
controller: PlanListingCtrl,
controllerAs: 'vm',
templateUrl: "scripts/directives/planListing/planListing.html";
}
};
return varDirective;
}
PlanListingCtrl.$inject = ['planListingService'];
function PlanListingCtrl(planListingService) {
...
}
planListingService.js
angular.module('myApp')
.factory('planListingService', planListingService);
planListingService.$inject = ['$q', 'ajax'];
function planListingService($q, ajax) {
...
}
ajax.js
angular.module('myApp')
.factory('ajax', ['backend', '$browser', 'settings', '$http', '$log',
function (backend, $browser, settings, $http, $log) {
...
planListing.spec.js
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
module('myApp');
module('my.templates');
beforeEach(module(function ($provide){
// This seems to have no effect at all, why?
$provide.service('planListingService', function () {
this.getAllPricePlans=function(){};
});
// I don't get the error if I uncomment this:
// $provide.service('ajax', function ($q) {
// this.getAllPricePlans=function(){};
// });
}));
beforeEach(function() {
module('myApp');
module('my.templates');
});
beforeEach(angular.mock.inject(function (_$compile_,_$rootScope_,_$controller_){
$compile=_$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
el = angular.element('<plan-listing></plan-listing>');
scope = $rootScope.$new();
$compile(el)(scope);
scope.$digest();
ctrl = el.controller('planListing');
scope = el.isolateScope() || el.scope();
vm = scope.vm;
}));
describe('testing compilation / linking', function (){
it('should have found directive and compiled template', function () {
expect(el).toBeDefined();
expect(el.html()).not.toEqual('');
expect(el.html()).toContain("plan-listing-section");
});
});
it('should have a defined controller',function(){
expect(ctrl).toBeDefined();
});
it('should have a defined scope',function(){
expect(ctrl).toBeDefined();
});
});
So why is that I need to mock up the 'ajax' service even though I am mocking up 'planListingService' which is the one calling the 'ajax' service?
Thanks!
I have been there... feels like bad start But i think your directive is depend on the service and you need to inject it in order to directive can work with this, Just by calling directive it doesn't mean that it's going to inject it in your test. It will look for it and if it's not injected it will give you error
you could do so before testing your directive
beforeEach(inject(function ($injector) {
yourService = $injector.get('yourService');
})
For documentation purposes, here is the answer (thanks #estus for noticing this):
Indeed the problem was related to the incorrect initialization of my modules. Instead of this:
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
module('myApp');
module('my.templates');
...
I should've done this:
describe('testing planListing.js',function(){
var el,ctrl,scope,vm;
var service;
beforeEach(module('myApp'));
beforeEach(module('my.templates'));
...
After that things started working again as expected.

Testing a directives controller when passed as a plain old function

In an effort to be more Angular 2.0 like I've decided to eliminate defining a controller in the traditional way:
e.g.
return {
restrict: 'E',
controller: 'MyCtrl',
controllerAs: 'vm',
bindToController: true,
scope: {},
templateUrl: 'path/to/tempate.html'
}
Here we can see that the controller name is passed as a string meaning that we have something like this defined somewhere:
app.controller('MyCtrl', function(...));
In a traditional test I'd just inject $controller service into the test to retrieve the MyCtrl controller. But I've decided to do this:
return {
restrict: 'E',
controller: MyCtrl,
controllerAs: 'vm',
bindToController: true,
scope: {},
templateUrl: 'path/to/tempate.html'
}
Here the only difference is that what is being passed to the controller declaration is a function called MyCtrl. This seems all well and good but do you go about retrieving this controller and testing it?
I tried doing this:
var $compile, $rootScope, $httpBackend, element, ctrl;
beforeEach(module('app'));
beforeEach(function() {
inject(function(_$compile_, _$rootScope_, _$httpBackend_) {
$compile = _$compile_;
$rootScope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
element = $compile('<directive></directive>')($rootScope);
ctrl = element[0].controller;
});
});
But in the above I get undefined coming back for the controller. Has anyone else made this move to be more like Angular 2.0? What differences have had to be made when it comes to testing?
Thanks
P.S The idea of the change in syntax is to make it easy to upgrade to Angular 2.0 when the time comes for us to do it.
EDIT
I've spent the last day or two trying to retrieve the controller and the only way that works seems to be just passing the controller in the traditional way. Here's a list of the ways I've tried (see the comments to determine what each outputs)
(function() {
'use strict';
/*
This part is used just for testing the html rendered
*/
describe('flRequestPasswordReset template: ', function() {
var $compile, $rootScope, $templateCache, template, element, ctrl;
var path = 'templates/auth/fl-request-password-reset/fl-request-password-reset.html';
beforeEach(module('app'));
beforeEach(module(path));
beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$templateCache = _$templateCache_;
// Using nghtml2js to retrieve our templates
template = $templateCache.get(path);
element = angular.element(template);
$compile(element)($rootScope.$new());
$rootScope.$digest();
// $rootScope has a reference to vm which is what my
// controllerAs defines but only contains one of the variables
// This returns undefined
ctrl = element.controller('flRequestPasswordReset');
}));
it('should be defined', function() {
expect(element).toBeDefined();
});
});
describe('flRequestPasswordReset: template with controller: ', function() {
var $compile, $rootScope, $httpBackEnd, element, ctrl;
var path = '/templates/auth/fl-request-password-reset/fl-request-password-reset.html';
beforeEach(module('app'));
beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_, _$httpBackend_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$httpBackEnd = _$httpBackend_;
$httpBackEnd.expectGET(path).respond({});
element = angular.element('<fl-request-password-reset></fl-request-password-reset>');
$compile(element)($rootScope.$new());
$rootScope.$digest();
// Uses alternate name for directive but still doesn't get the
// controller
ctrl = element.controller('fl-request-password-reset');
}));
it('should be defined', function() {
expect(element).toBeDefined();
});
});
/*
This part is used for testing the functionality of the controller by itself
*/
xdescribe('flRequestPasswordReset: just controller', function() {
var scope, ctrl;
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope;
// Instantiate the controller without the directive
ctrl = $controller('FlRequestPasswordResetController', {$scope: scope, $element: null});
}));
it('should be defined', function() {
expect(true).toBe(true);
});
/*
This works because I moved the controller back to the old way
*/
});
}());
According to my understanding of this question under pkozlowski.opensource answer you can use pass the controller as a function only if you don't encapsulate it inside the directive. This way it's exposed globally. This is a bad thing as it pollutes the global namespace however he seems to suggest wrapping it up when the application is built.
Is my understanding of this correct?

AngularJS: unit testing ui-router state changes

I am fairly new to unit testing in angular so bear with me please!
I have a $stateProvidor setup for my app and would like to test that the routing part does work correctly.
Say I have this sort of config:
angular.module("app.routing.config", []).config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state("home", {
url: "/",
templateUrl: "app/modules/home/page/home_page.html",
controller: "HomePageController",
resolve: {
setPageTitle: function($rootScope) {
return $rootScope.pageTitle = "Home";
}
}
}).state("somethingelse", {
url: "/",
templateUrl: "app/modules/home/page/somethingelse.html",
controller: "SomeThingElseController",
resolve: {
setPageTitle: function($rootScope) {
return $rootScope.pageTitle = "Some Thing Else";
}
}
});
return $urlRouterProvider.otherwise('/');
});
I came across this blog post on how to set up unit testing for a ui-router config, so I Have tried to adopt the same approach, here is my test I am trying out:
'use strict';
describe('UI-Router State Change Tests', function() {
var $location, $rootScope, $scope, $state, $templateCache;
beforeEach(module('app'));
beforeEach(inject(function(_$rootScope_, _$state_, _$templateCache_, _$location_) {
$rootScope = _$rootScope_;
$state = _$state_;
$templateCache = _$templateCache_;
$location = _$location_;
}));
describe('State Change: home', function() {
beforeEach(function() {
$templateCache.put(null, 'app/modules/home/page/home_page.html');
});
it('should go to the home state', function() {
$location.url('home');
$rootScope.$digest();
expect($state.href('home')).toEqual('#/');
expect($rootScope.pageTitle).toEqual('Home');
});
});
});
When running the test I am getting this error in the output:
Error: Unexpected request: GET app/modules/home/page/home_page.html
Clearly I am doing something wrong here, so any help or pointers would be much appreciated.
I did come across $httpBackend, is this something I should also be using here, so telling my test to expect a request to the html page my state change test is making?
This is almost certainly down to a partial html view (home_page.html) being loaded asynchronously during app / test runtime.
In order to handle this, you can preprocess your html partials into Javascript strings, which can then be loaded synchronously via your tests.
Have a look at karma-ng-html2js-preprocessor which should solve your problem.

Unit testing controller which uses $state.transitionTo

within a controller i have a function which uses $state.transitionTo to "redirect" to another state.
now i am stuck in testing this function, i get always the error Error: No such state 'state-two'. how can i test this? it its totally clear to me that the controller does not know anything about the other states, but how can i mock this state?
some code:
angular.module( 'mymodule.state-one', [
'ui.state'
])
.config(function config($stateProvider) {
$stateProvider.state('state-one', {
url: '/state-one',
views: {
'main': {
controller: 'MyCtrl',
templateUrl: 'mytemplate.tpl.html'
}
}
});
})
.controller('MyCtrl',
function ($scope, $state) {
$scope.testVar = false;
$scope.myFunc = function () {
$scope.testVar = true;
$state.transitionTo('state-two');
};
}
);
describe('- mymodule.state-one', function () {
var MyCtrl, scope
beforeEach(module('mymodule.state-one'));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
MyCtrl = $controller('MyCtrl', {
$scope: scope
});
}));
describe('- myFunc function', function () {
it('- should be a function', function () {
expect(typeof scope.myFunc).toBe('function');
});
it('- should test scope.testVar to true', function () {
scope.myFunc();
expect(scope.testVar).toBe(true);
expect(scope.testVar).not.toBe(false);
});
});
});
Disclaimer: I haven't done this myself, so I totally don't know if it will work and is what your are after.
From the top of my head, two solutions come to my mind.
1.) In your tests pre configure the $stateProvider to return a mocked state for the state-two That's also what the ui-router project itself does to test state transitions.
See: https://github.com/angular-ui/ui-router/blob/04d02d087b31091868c7fd64a33e3dfc1422d485/test/stateSpec.js#L29-L42
2.) catch and parse the exception and interpret it as fulfilled test if tries to get to state-two
The second approach seems very hackish, so I would vote for the first.
However, chances are that I totally got you wrong and should probably get some rest.
Solution code:
beforeEach(module(function ($stateProvider) {
$stateProvider.state('state-two', { url: '/' });
}));
I recently asked this question as a github issue and it was answered very helpfully.
https://github.com/angular-ui/ui-router/issues/537
You should do a $rootScope.$apply() and then be able to test. Note that by default if you use templateUrl you will get an "unexpected GET request" for the view, but you can resolve this by including your templates into your test.
'use strict';
describe('Controller: CourseCtrl', function () {
// load the controller's module
beforeEach(module('myApp'));
// load controller widgets/views/partials
var views = [
'views/course.html',
'views/main.html'
];
views.forEach(function(view) {
beforeEach(module(view));
});
var CourseCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
CourseCtrl = $controller('CourseCtrl', {
$scope: scope
});
}));
it('should should transition to main.course', inject(function ($state, $rootScope) {
$state.transitionTo('main.course');
$rootScope.$apply();
expect($state.current.name).toBe('main.course');
}));
});
Also if you want to expect on that the transition was made like so
expect(state.current.name).toEqual('state-two')
then you need to scope.$apply before the expect() for it to work

Resources