When setting up a unit test suite for an angular application using Karma/Jasmine, is it recommended to include the js with the app module's config function in the test's files?
I've read that it is suggested to exclude this from testing, however that seems awkward because there's often critical setup that happens in the config function that would prevent the application from working.
What's the best practice around this? Create a mock config function that does the same thing in a 'mocked' manner?
I'm running across this issue myself but want to understand the broader strategy:
How do unit test with angular-translate
In my application, I ended up using the following solution:
Define an "appBase" module with all the config and run functions that I want to run when unit-testing and create another "app" module which declares "appBase" module as a dependency and includes all the config and run functions that I don't what to run when unit-testing. Then all my unit tests use the "appBase" module, while the final application uses the "app" module. In code:
angular.module('appBase', ['dependencies'])
.config(function() {
// This one will run when unit-testing. Can also set-up mock data
// that will later be overridden by the "app" module
});
angular.module('app', ['appBase'])
.config(function() {
// This function will only run in real app, not in unit-tests.
});
Related
After reading hundreds of lines about browserify vs webpack and several how to of both I decided to go for webpack. The main reason it's because I liked the idea of bundling everything into a js file.
I have an angular project already working and I want to refactor it for webpack. The problem? My project is using angular 1.4.7, ng-animate and plain javascript (ES5) and all the tutorials and manuals are for ES6. I don't want to refactor my project so much. What's the way to go? I would like an example of each angular module : factory, directive, controller and so on. Many thanks
I typically have a feature.module.js file which has my module definition and requires all of the directives / services contained within the module. Also has the external dependancies.
/* module.js */
angular.module('my.module',['dependancy1', 'dependancy2']);
//External libraries
require('./dependancy1.module.js');
require('./dependancy2.module.js');
//Internal components
require('./thing.directive');
require('./thing.service';
/* service.js */
angular.module('my.module')
.directive('yourDir', function myDir(){...});
I'm dealing with the same problem now. And I found something that works (work in progress, but at least I can see progress). My steps:
Install yeoman
Run this angular-webpack generator. Select 'ES5' when asked (the other option is 'ES2015', which I guess is the same that 'ES6')
Start modifying the automatically generated boilerplate with your Angular code
Yes, you still need to learn about gulp and sass, but at least you can run a simple AngularJS app using the old ES5 syntax, and start modifying it.
I'm probably blogging about this. So, I'll update this answer then.
I tend to do this:
app.js:
require('/any/angular/deps');
var subModule = require('/some/sub/module');
var app = angular.module('myApp', []);
// pass the app module in sub modules to allow them to define their own config
subModule.configure(app);
/subModule/module.js:
var someSubDirective = require('./subDir/directive');
export function configure(app) {
someSubDirective.configure(app);
}
/subModule/subDir/directive.js:
export function configure(app) {
app.directive('myDir', myDir);
}
function myDir() {
}
My idea is to let all sub modules handle their own configuration, so declaring config or constant, factories or providers. Letting this then bubble up to the app.js. This means its really easy to delete a folder from your structure, because it is one line removal from it's parent module.
This also makes relevant file paths a lot shorter and easier to handle.
I'm looking for something that generates a boilerplate jasmine test for an angular controller. It seems you could pull the dependencies for the controller out and drop them into the spec and save some typing. I would be shocked if I were the first person to have this idea but I'm unable to find anything that does this, save a yeomen project that doesn't appear to work.
I've recently published my version of Angular JS unit test generator on npm - tleaf. Basically it tries to parse you source file looking for AngularJS units (controllers, services, etc) to extract information about unit name, module name and unit's dependencies. This information is used to create a unit test file, based on a template for this unit type. There is a default set of templates which have a pretty simple structure, it should be ok for general use. But it is also possible to create and use your own templates to generate unit test files. This is a very first version and I'll be happy to have any feedback.
I don't know of a generator for tests but I have two ideas.
Some editors provide templates for "repeated" code. Like Live Templates for Webstorm. There are multiple projects on github providing jasmine templates for it.
You could also check ng-describe. It removes the boilerplate and makes testing simpler. Here's an example form their github:
ngDescribe({
modules: 'A',
inject: ['$rootScope', 'foo'],
tests: function (deps) {
it('finally a test', function () {
deps.$rootScope.$apply();
expect(deps.foo).toEqual('bar');
});
}
});
I am using yeoman with generator-angular to generate our scripts & tests.
yo angular:directive myDirective
yo angular:service myService
yo angular:controller myController
etc..
will generate both the script and spec templates. I am using Karma and Jasmine.
You could also always write your own yeoman generator.
I found this thing and it does a lot of good gob:
https://www.npmjs.com/package/generator-yosapy
In a very basic Angular app we have
<head>...
<script src="app.js"></script>
<script src="maincontroller.js"></script>
App defines the app module, and controller then hangs off app as app.controller("MainController..") If the order of the two scripts is reversed, the app will throw an error "app is not defined".
Is there a way around this load order dependency? My fear is that as it becomes more complex, I will get script order dependencies. And perhaps I might want to load my scripts async as well.
Thank you.
See, for example, this plunker: http://plnkr.co/edit/kqVqTHxl4tc6mIV5bDbQ
I've defined SomeService in an own file, svc.js. I've defined the module and the main controller in app.js. And even though the MainController depends on that service that "loads later", the dependency is figured out, and injected. All you should worry about is: put the module definition first.
Also: Don't store the application inside a global variable called app, instead, use angular.module with the name of the module to retrieve a reference to it:
angular.module('SomeModuleName').controller(...)
Any kind of global variables are generally not a good practice.
Look at using Angular and Browserify or Angular and RequireJS. Browserify and RequireJS are module loaders that let you keep 1 script reference in your index.html.
Browserify is based on a build step that will bundle all your JS into 1 file.
RequireJS is an Asynchronous Module Definition (AMD) loader, that can load your files asynchronously.
Today I've adopting Browserify for my AngularJS project, but there is something that's very unclear to me. In all examples and blog posts I've seen stuff like this:
/app.js:
require('./messages');
angular.module('sling', ['sling.messages']);
/messages/index.js:
exports = angular.module('sling.messages', [])
.controller('MessagesListCtrl', require('./MessagesListCtrl'));
/messages/MessagesListCtrl.js:
module.exports = function() {
// ...
});
Sure, this works, but why do this? I've implemented it like this and that works absolutely fine too and feels more normal for an AngularJS project:
/app.js:
require('./messages');
angular.module('sling', ['sling.messages']);
/messages/index.js:
angular.module('sling.messages', []);
require('./MessagesListCtrl');
/messages/MessagesListCtrl.js:
angular.module('sling.messages').controller('MessagesListCtrl', function() {
// ...
});
In other words, I'm completely skipping the exports/module.exports, only using require to basically include the files with the controllers, services, filter, etc.
Am I doing this right? I mean it all works, but am I going to be in trouble later on?
The main (and honestly only) reason to use Browserify is if you want to have CommonJS modules (i.e. NodeJS modules) in the browser. A CommonJS module stays out of the global scope by having an implicit 'module' scope. You choose what to expose from the module scope (usually the entry point or primary function of your module) into by augmenting the 'exports' object that every module has.
So a 'real' CommonJS module looks like this.
File A:
// a.js
function doSomething() {
console.log("I am doing something");
}
module.exports = doSomething
File B:
// b.js
doSomething();
// Exception - attempt to call a non-existent function
File C:
// c.js
var doSomething = require('a');
doSomething();
// logs "I am doing something"
In the browser which has no module scope, a.js would be augmenting the global scope with the doSomething function, since it's declared as a global. Browserify works around this by wrapping each bundled module into a function wrapper, and supplying an 'exports' object to the included modules as an argument to this wrapper.
Enter AngularJS. You've got two approaches you can use here, of which I'm assuming from the fact that you're not using require('angular') is the first:
Include AngularJS as a hard-coded script available in your index.html before your Browserify-transpiled bundle script.
Shim AngularJS so that it is attached to an exports object rather than the window (using something like browserify-shim), then importing it using require like any other module.
I tend to prefer the second approach because it's kind of weird to use Browserify to give you module scope and then make the major dependency of your project a window global.
However, AngularJS already has its own dependency-injection driven module system. When you declare angularJS components, you're attaching them to module objects which themselves have been attached to the angular object. This means that the exports object for your angularJS module files is essentially redundant in the case of Angular, since as long as the file executes then the angular object will be augmented with your module and components.
You still need to 'require' the files since otherwise Browserify won't bundle them, they'll never execute, and they'll never augment the angular object with your modules. But you don't need to add anything to the exports for Angular, since the angular object is your exports.
So why did I spend all this time explaining how CommonJS modules and exports work? Because hopefully the other reason why you are using Browserify is to allow you to make use of modules hosted on NPM in your browser application. Most of those modules are non-angular commonJS modules, meaning their functionality is exposed via an exports. In this case it's important to capture their exports in a variable when you require them, such as I'm doing in c.js above. Similarly, if you write some modules and release them to NPM, your users are going to expect you'll be adding the entry point to your module to the exports object of the file declared as main in your package.json.
I'm trying to setup my AngularJS application to test out controllers, routes, templates and so on, but I'm having an issue getting some of the helper methods provided by the angular-mocks.js to work (namely module and inject).
I'm using testacular to load up the test suite with the following files added before the specs:
files = [
MOCHA,
MOCHA_ADAPTER,
'../application/lib/angular.min.js',
'./lib/angular/angular-mocks.js',
'./lib/angular/angular-scenario.js',
'../application/application.js',
'./lib/chai.js',
'./lib/chai-should.js',
'./lib/chai-expect.js',
'./spec/**/*.js'
];
So far so good, but when I run the tests I get this issue:
ReferenceError: Can't find variable: module
Not sure where this is loaded. Am I missing something?
First thing to check is that all those files are getting loaded in the test browser. It's surprisingly easy to get a path wrong in your config and not realize it. If you're running testacular with autowatch, you can navigate to http://localhost:9876/context.html with a browser and use developer tools inspect elements/resources/network and see if anything is missing.
If everything is good there and you're still having problems, post some of your test code and I'll take a look.
UPDATE: It appears (strangely) from the comments in the source for angular-mocks.js (line 1635) that window.module is only available with Jasmine. It looks like you're using Mocha instead of Jasmine. This is very likely the culprit.
ANSWER:
I can't rightly take credit for this Matsko, since you figured it out yourself... but it turns out that the current AngularJS stable download and angular-seed contain an older version of ngMock that doesn't support Mocha. Manually replacing the mock file with the latest from the github repo solves the problem. Glad I could help ;-)
I ran into this issue today and I wanted to provide others with a complete summary of the required steps to get this working. First let's say you have a module named myApp. Inside that that module there is a service called myModel. the myModel service has a method named getItems().
Currently, the angular-mocks.js (I am using AngularJS 1.0.6) does not support Mocha. You will need to visit this link and replace the 1.0.6 version with the one in the master branch from the AngularJS GitHub project. An easy way to do this (if you have wget) is wget https://raw.github.com/angular/angular.js/master/src/ngMock/angular-mocks.js in the correct directory. If you use a properly configured Sublime or vim it can also easily pull it down for you.
Make sure your karma.conf.js file includes the angular-mocks.js file in files array
Somewhere in your tests.js file (maybe at the top level describe) include beforeEach(module('myApp')); or whatever you named your main module.
If you plan to use the service (or whatever you want to include in the test) in more than one place you can call another beforeEach where needed like this:
beforeEach(inject(function(myModel) {
mymodel = myModel;
}));
otherwise you just can inject where it is needed. Now the mymodel variable (notice this is the variable you assigned in the beforeEach above) will be available to you for testing in your next blocks. For example, you can now write:
describe('when fetched', function() {
it('should return 3 items', function() {
// console.log(mymodel.getItems());
expect(mymodel.getItems()).to.have.length(3);
});
});