Category Archives: AngularJS

AngularJS, Karma and debugging unit tests in WebStorm or IntelliJ IDEA

Recently i had to debug few Javascript unit tests in WebStorm IDE and was wondering if it’ll be as easy of an experience as it is in case of Java and IntelliJ IDEA (where i originally come from).

WebStorm 6 doesn’t offer native Karma test runner support (ver 7 which is currently in EAP, does – details here), but using a NodeJS plug-in you can execute any kind of NodeJS application (Karma included).

 

OK, what we’ll need in this exercise is following:

  • One of two JetBrains IDE’s, either:
    • WebStorm (great for JavaScript code) or
    • IntelliJ IDEA (Java’s no. 1 IDE)
  • NodeJS plug-in installed in the IDE:
    • WebStorm comes having it already pre-installed
    • in case of IDEA (Ultimate version, because Community Edition doesn’t have the required JavaScript plug-in for it to work, see here) the plug-in can be downloaded from here.
  • NodeJS environment which can be downloaded from here.
  • Karma (old name Testacular) test runner installed (“npm install -g karma”) that allows running unit (or E2E) tests in one of these browsers:
    • Chrome
    • ChromeCanary
    • Firefox
    • Opera
    • Safari (only Mac)
    • PhantomJS
    • IE (only Windows)
  • Chrome/Firefox “JetBrains IDE Support” extension (required for debugging) that can be downloaded from here.

 

Installing the NodeJS plug-in in IntelliJ IDEA:

  • Open “Settings” dialog (File -> Settings… in the menu bar)
  • Select “Plugins” (under “IDE Settings”)
  • Click “Browse repositories…”
  • Click “Download and Install” on the “NodeJS” plug-in
  • Press “Restart” when asked, to restart the IDE

 

Configuring IDE to execute Karma test in NodeJS using the plug-in:

  • Open the Run/Debug Configuration dialog by selecting “Edit Configurations” in the Run area of the main toolbar of WebStorm.
  • Add the following two configurations (picture below):
    • “Karma Run”: to perform a “single run” of your unit tests.
    • “Karma Server”: to start Karma in “Continuous Integration” mode (automatic re-runs of your tests whenever files change).
  • Configure the “Karma Run” configuration:
    • Press the “+” button in the top-left of the “Run/Debug Configurations” dialog.
    • Select “Node.js” in the list
    • Fill in the following fields:
      • Name: enter “Karma Run”
      • Path to Node: absolute path to NodeJS executable (i.e. “c:\NodeJS\node.exe”)
      • Working Directory: absolute path of your AngularJS application (i.e. “C:\MyProjects\AngularApp”)
      • Path to Node App JS File: Should point to the (globally, ie. -g) installed “Karma” NodeJs executable (i.e. “C:\Users\…\AppData\Roaming\npm\node_modules\karma\bin\karma”)
      • Application Parameters: run karma.conf.js –single-run –no-auto-watch –reporters dots
    • Press “Apply”
  • Configure the “Karma Server” configuration:
    • Essentially take the same steps as while configuring “Karma Run”, changing only the following:
      • Name: enter “Karma Run”
      • Application Parametersstart karma.conf.js –no-single-run –auto-watch –reporters dots
  • Configure the “Karma Debug” configuration to allow debugging of Karma unit tests
    • Press the “+” button in the top-left of the “Run/Debug Configurations” dialog.
    • Select “JavaScript Debug -> Remote” in the list
    • Fill in the following fields:
      • Name: enter “Karma Debug”
      • URL to openhttp://localhost:8100/debug.html (port number depends on your configuration in karma.conf.js file (passed as an “Application Parameter” in the previous two configurations)
      • Browser: chose either Chrome or Firefox
      • Set the “Remote URL” field to point to “http://localhost:8100/base

        Karma Debug configuration in IntelliJ WebStorm

 

Finally, run your “Karma Server” configuration and while it’s working in the background (–auto-watch mode), set debugging breakpoints in your code and fire “Karma Debug” configuration.

That’s it. Hope this small guide turns out to be helpful.

 

Resources:

Advertisement

Yeoman, Karma and e2e testing

Imagine my surprise when i first looked at the Gruntfile.js of my newly generated angular app and found out that Yeoman is configuring by default only the karma:unit section, missing out the karma:e2e piece. I know the product is fairly new, but i would expect it to be a little more “mature” (spent quite some time figuring out why my e2e tests aren’t working and how to solve the problem).

 

Anyway, in order to run your e2e tests, you have to do the following:

  1. Update the Gruntfile.js
karma: {
    e2e: {
        configFile: 'karma-e2e.conf.js'
    },
    unit: { ... }
},
  1. Update the karma-e2e.conf.js
urlRoot = '/e2e/';
proxies = {
    // port has to be the same your web server is running on
    '/': 'http://localhost:9000'
};

 

Also, if you want Grunt to run your e2e tests without the need to manually run the web server first, you can additionally define the following task:

grunt.registerTask('test:e2e', function () {
    grunt.task.run([
        'clean:server',
        'concurrent:server',
        'connect:livereload',
        'open',
        'karma:e2e'
    ]);
});

 

(and “test:unit” task accordingly, for consistency sake):

grunt.registerTask('test:unit', [
    'karma:unit'
]);

AngularJS basics

I’ve been reading a great introductory book on AngularJS recently (authored by Brad Green and Shyam Seshadri) and thought i’ll share with you some of my notes (prior to going further with reading however, i strongly advise to watch few videos on the subject from the wonderful egghead.io website):

  • The role of the server:
    • Traditional Web Apps – create HTML pages by assembling components (header, content, footer, etc.), joining it with the data and then shipping it all over the wire to the browser to display.
    • AngularJS Apps – serve static resources (HTML templates, media, etc.) and data to the browser which takes care of assembling.
  • “By structuring your application with Angular, your application’s templates are kept separate from the data that populates them. The result of this is that these templates are cacheable. Only new data need to come down to the browser after the first load. Just as with JavaScript, images, CSS, and other resources, caching these templates can give your application even better performance.”
  • Model-View-Controller (MVC)
    • application architecture that separates the code between:
      • model – managing data (which is stored in object properties)
      • controller – managing the application logic (javascript classes)
      • view – presenting the data to the user (DOM)
    • “The view gets data from the model to display to the user. When a user interacts with the application by clicking or typing, the controller responds by changing data in the model. Finally, the model notifies the view that a change has occurred so that it can update what it displays.”
  • Data binding
    • “…just declare which parts of the UI map to which JavaScript properties and have them sync automatically.”
    • use double-curly syntax {{someText}} or ng-bind=”someText” directive to insert new content into an existing HTML template (with the double-curly syntax, on the very first page load of your application’s index.html, there’s a chance that your user will see the un-rendered template before Angular has a chance to replace the curlies with your data).
  • Dependency Injection (definition taken from Craig Walls “Spring in Action” 3rd ed.)
    • Any nontrivial application is made up of two or more classes that collaborate with each other to perform some business logic. Traditionally, each object is responsible for obtaining its own references to the objects it collaborates with (its dependencies). This can lead to code which is:
      • highly coupled
      • hard-to-test
      • difficult to reuse
      • difficult to understand
      • code that  typically exhibits “whack-a-mole” bug behavior (fixing one bug results in the creation of one or more new bugs)
    • With DI, on the other hand, objects are given their dependencies at creation time by some third party (usually specialized DI framework) that coordinates each object in the system. Objects aren’t expected to create or obtain their dependencies—dependencies are injected into the objects that need them.
  • Directives:
    • in AngularJS you can write your templates as HTML, because at the core of the framework there is a powerful DOM transformation engine included that lets you extend HTML’s syntax (ng-app, ng-controller, ng-model and similar directives).
  • To invoke AngularJS you must:
    • Load the angular.js library (eg. from Google’s CDN (content delivery network), ie.: https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js”)
    • Tell Angular which part of the DOM it should manage with the ng-app directive (eg. <html ng-app>)
  • Basic AngularJS app startup flow:
    • A user requests the first page of your application.
    • The user’s browser makes an HTTP connection to your server and loads the index.html page containing your template.
    • Angular loads into the page, waits for the page to be fully loaded, and then looks for ng-app to define its template boundaries.
    • Angular traverses the template and looks for directives and bindings. This results in registration of listeners and DOM manipulation, as well as fetching initial data from the server. The end result of this work is that the app is bootstrapped and the template is converted into view as a DOM.
    • You connect to your server to load additional data you need to show the user as needed.
  • Rationale behind writing “unobtrusive JavaScript” code (eg. not using click, mousedown, and other such inline event handlers in your HTML) and how AngularJS re-examine the problem:
    • Not everyone’s browser supports JavaScript. Let everyone see all of your content and use your app without needing to execute code in the browser.
      • this is no longer true as if you’re running a browser without JavaScript, you’re relegated to sites created in the ’90s
    • Some folks use browsers that work differently. Visually impaired folks who use screen-readers and some mobile phone users can’t use sites with JavaScript.
      • modern screen-readers have caught up. With proper use of ARIA semantic tags, you can make very rich UIs easily accessible. Mobile phones now run JavaScript on par with desktop machines.
    • Javascript works differently across different platforms. IE is usually the culprit here. You need to put in different event-handling code depending on the browser.
      • Angular has an equivalent in the form of ng-eventhandler=”expression” where eventhandler would be replaced by click, mousedown, change, and so on. AngularJS directives differ from their event handler predecessors in that they b

        ehave the same in every browser. Angular takes care of the differences for you.

    • These event handlers reference functions in the global namespace. It will cause you headaches when you try to integrate other libraries with functions of the same names.
      • Similarly to the previous point, AngularJS equivalent eventhandlers do not operate on the global namespace. The expressions you specify can only access functions and data that is in the scope of the element’s controller.
    • These event handlers combine structure and behavior. This makes your code more difficult to maintain, extend, and understand.
      • There’s a simple acid test we can use to figure out if our system suffers from this coupling: can we create a unit test for our app logic that doesn’t require the DOM to be present? In Angular, yes we can write controllers containing our business logic without having references to the DOM.
  • While angular is a client-side-only technology and it’s possible to create angular web apps that don’t require a back-end server at all, it is recommended to host the project files using a local web-server during development to avoid issues with security restrictions (sandbox) in browsers. The sandbox implementation varies between browsers, but quite often prevents things like cookies, xhr, etc to function properly when an html page is opened via file:// scheme instead of http://.