AngularJS + Karma + Jasmine: beforeEach() - angular.module vs angular.mock.module - angularjs

I am new to Jasmine and Karma testing.
I am trying to Unit test AngularJs Service.
While writing Specs, I came around two types of Code for injecting Module.
1st Type
beforeEach(angular.mock.module("app"));
2nd Type
beforeEach(function () {
angular.module("app");
});
Can anybody explain the difference between the above two in simple plain English with a simple example.
And also, which code to use in what kind of scenario?
I tried to Google it, but was not able to find a proper answer.
Thanks :)

From angular-mocks.js
window.module = angular.mock.module = function() {
...
More info:
http://www.bradoncode.com/blog/2015/05/24/ngmock-fundamentals-angularjs-unit-testing/
https://github.com/bbraithwaite/angular.js/blob/master/src/ngMock/angular-mocks.js#L2259
https://gist.github.com/trinitroglycerin/68754b920df83a977f12#gistcomment-2851474
To enable usage of 'module' add this to webpack configuration:
const webpack = require('webpack');
module.exports = function(config) {
config.set({
//...config
webpack: {
//...webpack config
plugins: [
new webpack.DefinePlugin({
module: 'window.module',
}),
],
}
});
};

Related

ReferenceError : AngularJS is not defined on Karma with Jasmine unit testing

I'm actually stuck since few hours, my boss wants me to proceed to unit testing on the functionalities i coded last week, when I do my karma start karma.conf.js it gives me the following issue :
"message": "An error was thrown in afterAll\nUncaught ReferenceError: angular is not defined",
my files structure :
webapp
-www
-services
api.service.js
-test
test.test.js
karma.conf.js
essential of karma.conf.js :
module.exports = function(config) {
config.set({
files: [
'www/js/services/api.service.js',
'test/**/*.test.js'
],
})
}
I think Karma doesn't find my api.service.js but i don't know why, the angularjs code actually work in the webapp.
content of my test.test.js :
(function() {
'use strict';
describe('apiService', function() {
it('should return an array of object', function() {
var artist = typeof getArtist(118680); // MGMT Artist
console.log(artist);
expect(artist).toEqual('array');
});
});
})();
getArtist is the function I need to test situated in api.service.js.
Thank you,
Paul.
Karma needs the angular file to test angularJS code. You need to include it in the files array in the karma configuration.
module.exports = function(config) {
config.set({
files: [
'path-to-angular.js',
'www/js/services/api.service.js',
'test/**/*.test.js'
],
})
}

Can't setup Karma and Jasmine for Angularjs

I've tried several tutorials and looked at many of the solutions provided here. I am new to Angular and currently trying to set up testing for a rather big SPA.
Following this tutorial I have completed:
Angularjs application setup
Karma setup
Our first test
The karma config file is basically the default content, with some references in files and exclude:
// list of files/patterns to load in the browser
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'app/app.js',
'JavaScript.spec.js'
],
I reinstalled the entire test implementation and now the simple test works again. But trying to write a test for a controller does not work:
here is the error message
I changed the path referencing the bower_components and app files in the karma config file. Now the shell running karma returns an error message from the app.js file, saying:
Uncaught ReferenceError: Logging is not defined
Writing a test identical to the one from doucmentation, gives the following error:
Here is the test code:
describe('nyKladdController', function () {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function (_$controller_) {
$controller = _$controller_;
}));
describe('$scope.mixTable', function () {
it('is false', function () {
var $scope = {};
var controller = $controller('nyKladdController', { $scope: $scope });
expect($scope.mixTable).toBeFalsy();
});
});
});
As you can see from the error message: after the app module, the test file start loading the app dependencies. Here is the app.js file:
(function () {
'use strict';
angular.module('app', [
'ngAnimate', 'ngRoute', 'ngSanitize', 'ngResource', 'ngMessages',
'AdalAngular', 'config', 'angular.filter',
'ui.bootstrap', 'ui.mask', 'ui.select', 'ui.validate',
'angular-loading-bar', 'ui.tree', 'ui.tree-filter', 'checklist-model'
]);
})();
In other words: how can i get my tests to load the app dependecies as well.
I had to load in all the app dependencies from app.js in to karma.config file. Now the files array in karma config looks like this:
// list of files / patterns to load in the browser
files: [
//bower modules
'./bower_components/angular/angular.js',
'./bower_components/angular-mocks/angular-mocks.js',
'./bower_components/angular-ui-mask/src/mask.js',
'./bower_components/angular-ui-select/dist/select.js',
'./bower_components/angular-ui-tree-filter/dist/angular-ui-tree-filter.js',
'./bower_components/angular-ui-tree/dist/angular-ui-tree.js',
'./bower_components/angular-ui-validate/dist/validate.js',
'./bower_components/angular-loading-bar/build/loading-bar.js',
// node modules
'./node_modules/angular-animate/angular-animate.js',
'./node_modules/angular-route/angular-route.js',
'./node_modules/angular-sanitize/angular-sanitize.js',
'./node_modules/angular-resource/angular-resource.js',
'./node_modules/angular-messages/angular-messages.js',
'./node_modules/adal-angular/dist/adal-angular.min.js',
'./node_modules/angular-filter/dist/angular-filter.js',
'./node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js',
'./node_modules/bower-config/lib/Config.js',
'./node_modules/checklist-model/checklist-model.js',
//app file
'./app/app.js',
'./app/common/config/config.js',
//test files etc..
'JavaScript.spec.js',
'./app/produkt/ny/controllers/*.js' // tester å hente inn controller som refereres i test filen
],
This may be because Karma is loading the source files in the wrong order. For Angular to work properly, each module must be loaded before any component, services, etc. associated with that module.
To fix this, you can change your Karma configuration to ensure your module files are loaded first.
// list of files / patterns to load in the browser
files: [
'../../bower_components/angular/angular.js',
'../../bower_components/angular-mocks/angular-mocks.js',
'app/**/*.module.js',
'app/**/*.js'
],
This is assuming you're using some kind of naming convention for angular modules (like *.module.js as in the above example). Otherwise you'll have to list the paths to the modules individually.
add beforeEach(module("your-module-name")); => your angular application name from app.js"
describe('check a controller', function () {
beforeEach(module("your module name")); // I think you missed this.
var scope, checkController;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.new();
checkController = function () {
return $controller('nyKladdController', {
'$scope': scope
});
};
}));
it('has a dummy spec to test 2 + 2', function () {
// An intentionally failing test. No code within expect() will never equal 4.
expect(2 + 2).toEqual(4);
});
});
karma.conf
files: [ // sample order. Include your files accordingly
'src/bower_components/angular/angular.min.js',
'src/bower_components/angular-mocks/angular-mocks.js',
// some sample libraries
'src/bower_components/angular-cookies/angular-cookies.min.js',
'src/bower_components/angular-ui-router/release/angular-ui-router.min.js',
'src/bower_components/angular-resource/angular-resource.min.js',
'src/bower_components/angular-sanitize/angular-sanitize.min.js',
'src/bower_components/angular-loading-bar/build/loading-bar.min.js',
'src/app/app.js',
'src/app/**/*.js',
'tests/unit/**/*.spec.js'
],
exclude: []

How to run AngularJS unit tests against the minified app with Webpack and Karma

I'm working on an AngularJS application. We have a large existing codebase which we are moving to Webpack 2 in preparation for ng-upgrade. However we can't figure out how to run our Jasmine unit tests against the minified app.
We have modeled our application after this preboot repository.
We uglify the app using this in webpack.config.js:
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({sourceMap: false})
);
karma.conf.js basically looks like this:
module.exports = function karmaConfig (config) {
config.set({
frameworks: ['jasmine'],
files: ['src/tests.webpack.js'],
preprocessors: {
'src/tests.webpack.js': ['webpack', 'sourcemap']
},
webpack: require('./webpack.config'),
...
});
};
src/tests.webpack.js looks like this:
import 'angular';
import 'angular-mocks/angular-mocks';
const context = require.context('..', true, /\.spec.js$/);
context.keys().forEach(context);
Each .spec.js file uses import to load the actual Angular application, then does some injection like this:
beforeEach(inject(function($rootScope){
...
}));
This is where we have a problem. Our unit tests are being minified. $rootScope is minified to e, which the injector fails to find. We want to minify only the app, not test files. How can we do this?
Whenever you have this code in your tests:
beforeEach(inject(function($rootScope){
...
}));
minification changes the variable names so they might look something like:
beforeEach(inject(function($a){
...
}));
The problem is that angular inspects the angular parameters at runtime, and has no idea what a is!!
The solution:
beforeEach(inject(['$rootScope', function($rootScope){
...
}]));
This way even if the parameter names are mangled, the string $rootScope gives angular a hint what each parameter refers to

Karma: how to unit test the filter with dependencies?

I'm trying to unit test filter which has dependency on another service. I've read many answers regarding this problem but nothing solved mine.
I use karma/Jasmine
the conf as follow
// Karma configuration
// Generated on Thu Nov 26 2015 07:47:24 GMT+0100 (Västeuropa, normaltid)
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'
],
// list of files / patterns to load in the browser
files: [
'lib/angular.min.js',
'lib/angular-mocks.js',
'test.js',
'app/js/services/*.js',
'app/js/filters/*.js'
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma- preprocessor
preprocessors: {
},
my application as follow
// filter
var app=angular.module('app.filter',[]);
app.filter("filterName",['serviceName',function(ServiceName){
return function(input1,input2)
{
//some code
}]);
// service
var app=angular.module('app.service',[]);
app.service("serviceName", [$log,$windows,$route',function($log,$windows,$route){
//some functions
}]);
the test
describe('test filter with services dependencies',function()
{
beforeEach(module('app.service'));
beforeEach(module('app.filter'));
var FilterNAMe, ServiceNAMe ;
beforeEach(inject(function(_ServiceNAMe _,_FilterNAMe_) // i get error here
{
FilterNAMe = _FilterNAMe_;
ServiceNAMe= _ServiceNAMe_;
}));
it('',function()
{
//some test;
});
});
and I get the following error
error:[$injector:unpr]
http://errors.angularjs.org/1.4.0-rc.2/$injector/unpr?=%24serviceNameProvider
Error: Declaration location at windows.inject.angular.mock.inject
first it is not needed to module( app.service),where the dependency service is located
otherwise, use provide function to provide the service when invoking the current module
var $filter, FilterNAMe ;
beforeEach(function () {
angular.mock.module("app.filter", function ($provide) {
$provide.value('serviceName');
});
});
then inject the filter
beforeEach( inject(function ( $filter) { //<-- Get the filter provider
FilterNAMe =$filter('FilterNAMe ',{});
}));
see the answer
Injecting dependent services when unit testing AngularJS services

Unit Testing AngularJS directive with templateUrl

Using AngularJS.
Have a directive.
Directive defines templateUrl.
Directive needs unit testing.
Currently unit testing with Jasmine.
This recommends code like:
describe('module: my.module', function () {
beforeEach(module('my.module'));
describe('my-directive directive', function () {
var scope, $compile;
beforeEach(inject(function (_$rootScope_, _$compile_, $injector) {
scope = _$rootScope_;
$compile = _$compile_;
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('path/to/template.html').passThrough();
}));
describe('test', function () {
var element;
beforeEach(function () {
element = $compile(
'<my-directive></my-directive>')(scope);
angular.element(document.body).append(element);
});
afterEach(function () {
element.remove();
});
it('test', function () {
expect(element.html()).toBe('asdf');
});
});
});
});
Running code in Jasmine.
Getting error:
TypeError: Object #<Object> has no method 'passThrough'
templateUrl needs loading as-is
Cannot use respond
May be related to ngMock use rather than ngMockE2E use.
You're correct that it's related to ngMock. The ngMock module is automatically loaded for every Angular test, and it initializes the mock $httpBackend to handle any use of the $http service, which includes template fetching. The template system tries to load the template through $http and it becomes an "unexpected request" to the mock.
What you need a way to pre-load the templates into the $templateCache so that they're already available when Angular asks for them, without using $http.
The Preferred Solution: Karma
If you're using Karma to run your tests (and you should be), you can configure it to load the templates for you with the ng-html2js preprocessor. Ng-html2js reads the HTML files you specify and converts them into an Angular module that pre-loads the $templateCache.
Step 1: Enable and configure the preprocessor in your karma.conf.js
// karma.conf.js
preprocessors: {
"path/to/templates/**/*.html": ["ng-html2js"]
},
ngHtml2JsPreprocessor: {
// If your build process changes the path to your templates,
// use stripPrefix and prependPrefix to adjust it.
stripPrefix: "source/path/to/templates/.*/",
prependPrefix: "web/path/to/templates/",
// the name of the Angular module to create
moduleName: "my.templates"
},
If you are using Yeoman to scaffold your app this config will work
plugins: [
'karma-phantomjs-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor'
],
preprocessors: {
'app/views/*.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
stripPrefix: 'app/',
moduleName: 'my.templates'
},
Step 2: Use the module in your tests
// my-test.js
beforeEach(module("my.templates")); // load new module containing templates
For a complete example, look at this canonical example from Angular test guru Vojta Jina. It includes an entire setup: karma config, templates, and tests.
A Non-Karma Solution
If you do not use Karma for whatever reason (I had an inflexible build process in legacy app) and are just testing in a browser, I have found that you can get around ngMock's takeover of $httpBackend by using a raw XHR to fetch the template for real and insert it into the $templateCache. This solution is much less flexible, but it gets the job done for now.
// my-test.js
// Make template available to unit tests without Karma
//
// Disclaimer: Not using Karma may result in bad karma.
beforeEach(inject(function($templateCache) {
var directiveTemplate = null;
var req = new XMLHttpRequest();
req.onload = function() {
directiveTemplate = this.responseText;
};
// Note that the relative path may be different from your unit test HTML file.
// Using `false` as the third parameter to open() makes the operation synchronous.
// Gentle reminder that boolean parameters are not the best API choice.
req.open("get", "../../partials/directiveTemplate.html", false);
req.send();
$templateCache.put("partials/directiveTemplate.html", directiveTemplate);
}));
Seriously, though. Use Karma. It takes a little work to set up, but it lets you run all your tests, in multiple browsers at once, from the command line. So you can have it as part of your continuous integration system, and/or you can make it a shortcut key from your editor. Much better than alt-tab-refresh-ad-infinitum.
What I ended up doing was getting the template cache and putting the view in there. I don't have control over not using ngMock, it turns out:
beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
$scope = _$rootScope_;
$compile = _$compile_;
$templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));
This initial problem can be solved by adding this:
beforeEach(angular.mock.module('ngMockE2E'));
That's because it tries to find $httpBackend in ngMock module by default and it's not full.
The solution I reached needs jasmine-jquery.js and a proxy server.
I followed these steps:
In karma.conf:
add jasmine-jquery.js to your files
files = [
JASMINE,
JASMINE_ADAPTER,
...,
jasmine-jquery-1.3.1,
...
]
add a proxy server that will server your fixtures
proxies = {
'/' : 'http://localhost:3502/'
};
In your spec
describe('MySpec', function() {
var $scope, template;
jasmine.getFixtures().fixturesPath = 'public/partials/'; //custom path so you can serve the real template you use on the app
beforeEach(function() {
template = angular.element('');
module('project');
inject(function($injector, $controller, $rootScope, $compile, $templateCache) {
$templateCache.put('partials/resources-list.html', jasmine.getFixtures().getFixtureHtml_('resources-list.html')); //loadFixture function doesn't return a string
$scope = $rootScope.$new();
$compile(template)($scope);
$scope.$apply();
})
});
});
Run a server on your app's root directory
python -m SimpleHTTPServer 3502
Run karma.
It took my a while to figure this out, having to search many posts, I think the documentation about this should be clearer, as it is such an important issue.
My solution:
test/karma-utils.js:
function httpGetSync(filePath) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/base/app/" + filePath, false);
xhr.send();
return xhr.responseText;
}
function preloadTemplate(path) {
return inject(function ($templateCache) {
var response = httpGetSync(path);
$templateCache.put(path, response);
});
}
karma.config.js:
files: [
//(...)
'test/karma-utils.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
the test:
'use strict';
describe('Directive: gowiliEvent', function () {
// load the directive's module
beforeEach(module('frontendSrcApp'));
var element,
scope;
beforeEach(preloadTemplate('views/directives/event.html'));
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should exist', inject(function ($compile) {
element = angular.element('<event></-event>');
element = $compile(element)(scope);
scope.$digest();
expect(element.html()).toContain('div');
}));
});
If you are using Grunt, you can use grunt-angular-templates. It loads your templates in the templateCache and it's tranparent to your specs configuration.
My sample config:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
ngtemplates: {
myapp: {
options: {
base: 'public/partials',
prepend: 'partials/',
module: 'project'
},
src: 'public/partials/*.html',
dest: 'spec/javascripts/angular/helpers/templates.js'
}
},
watch: {
templates: {
files: ['public/partials/*.html'],
tasks: ['ngtemplates']
}
}
});
grunt.loadNpmTasks('grunt-angular-templates');
grunt.loadNpmTasks('grunt-contrib-watch');
};
I solved the same problem in a slightly different way than the chosen solution.
First, I installed and configured the ng-html2js plugin for
karma. In the karma.conf.js file :
preprocessors: {
'path/to/templates/**/*.html': 'ng-html2js'
},
ngHtml2JsPreprocessor: {
// you might need to strip the main directory prefix in the URL request
stripPrefix: 'path/'
}
Then I loaded the module created in the beforeEach.
In your Spec.js file :
beforeEach(module('myApp', 'to/templates/myTemplate.html'));
Then I used $templateCache.get to store it into a variable.
In your Spec.js file :
var element,
$scope,
template;
beforeEach(inject(function($rootScope, $compile, $templateCache) {
$scope = $rootScope.$new();
element = $compile('<div my-directive></div>')($scope);
template = $templateCache.get('to/templates/myTemplate.html');
$scope.$digest();
}));
Finally, I tested it this way.
In your Spec.js file:
describe('element', function() {
it('should contain the template', function() {
expect(element.html()).toMatch(template);
});
});
To load the template html dynamically into $templateCache you could just use html2js karma pre-processor, as explained here
this boils down to adding templates '.html' to your files in the conf.js file
as well
preprocessors = {
'.html': 'html2js'
};
and use
beforeEach(module('..'));
beforeEach(module('...html', '...html'));
into your js testing file
if you're using Karma, consider using karma-ng-html2js-preprocessor to pre-compile your external HTML templates and avoid having Angular try to HTTP GET them during test execution. I struggled with this for a couple of ours - in my case templateUrl's partial paths resolved during normal app execution but not during tests - due to differences in app vs. test dir structures.
If you are using the jasmine-maven-plugin together with RequireJS you can use the text plugin to load the template content into a variable and then put it in the template cache.
define(['angular', 'text!path/to/template.html', 'angular-route', 'angular-mocks'], function(ng, directiveTemplate) {
"use strict";
describe('Directive TestSuite', function () {
beforeEach(inject(function( $templateCache) {
$templateCache.put("path/to/template.html", directiveTemplate);
}));
});
});
If you use requirejs in your tests then you can use the 'text' plugin to pull in the html template and put it in the $templateCache.
require(["text!template.html", "module-file"], function (templateHtml){
describe("Thing", function () {
var element, scope;
beforeEach(module('module'));
beforeEach(inject(function($templateCache, $rootScope, $compile){
// VOILA!
$templateCache.put('/path/to/the/template.html', templateHtml);
element = angular.element('<my-thing></my-thing>');
scope = $rootScope;
$compile(element)(scope);
scope.$digest();
}));
});
});
I resolve this issue with compiling all templates to templatecache.
I'm using gulp, you can find similar solution for grunt too.
My templateUrls in directives, modals looks like
`templateUrl: '/templates/directives/sidebar/tree.html'`
Add a new npm package in my package.json
"gulp-angular-templatecache": "1.*"
In gulp file add templatecache and a new task:
var templateCache = require('gulp-angular-templatecache');
...
...
gulp.task('compileTemplates', function () {
gulp.src([
'./app/templates/**/*.html'
]).pipe(templateCache('templates.js', {
transformUrl: function (url) {
return '/templates/' + url;
}
}))
.pipe(gulp.dest('wwwroot/assets/js'));
});
Add all js files in index.html
<script src="/assets/js/lib.js"></script>
<script src="/assets/js/app.js"></script>
<script src="/assets/js/templates.js"></script>
Enjoy!

Resources