Configuring Karma to test Angular apps using RequireJS

Installing and configuring a testing environment for an Angular application hasn’t been very straightforward for me but here are the steps I took (the right ones, at least).

Configuring Karma

First of all, we need to have installed and working: Node.js + Karma + Mocha + Chai + Sinon + Karma-RequireJs (the plugin for Karma, not the standard library).

Once that works, we still need to configure it all to test our app, which is a big angular app and uses RequireJS to load its modules. First of all, we can configure Karma by executing “karma init” on the console (make sure you do that on the main folder of your project!!). That will give us a config file with the basic config done.

We wanted to use Mocha, Chai and Sinon, apart from RequireJS, so let’s make sure that Karma knows it needs to use those frameworks:
frameworks: ['mocha', 'requirejs', 'sinon-chai']

Note: I had some problems when installing those libs globally to get’em out of the local files and make the project lighter, so ended up installing sinon and chai individually:
frameworks: ['mocha', 'requirejs', 'chai', 'sinon']

Karma is a javascript server, it works similar to what Apache or IIS would but to tell it which files are part of our project, and on being so, the browser can access, we need to do that at the “files” property:

    files: [
      { pattern: 'MyProject/Scripts/angular.js', included: true },
      { pattern: 'MyProject.Tests/lib/angular-mocks.js', included: true },
      { pattern: 'test-main.js', included: true },
      { pattern: 'MyProject/**/*.js', included: false },
      { pattern: 'MyProject.Tests/**/*.js', included: true }
    ],

Have in mind that the files loaded through those patterns won’t be sent to the browser unless we set the “included” property to true. Loading and not sending them to the browser allows them to be available, so we can always make a call using requireJS or any other to those files and get them.

Here, in theory, I should just send the main requireJS config file to the browser, which in executing, will know what to do step to step, but we got many problems tryign to do that and ended up deciding to send to the browser all the basic files needed to test, these are:

  • Angular: The main library to execute any angular code.
  • angular-mocks: This allows us to test an angular app, but requires angular.js as it’s an extension of that one.
  • test-main.js: This is the requireJS bootstrap file, the one we were expected to send.
  • Tests: These ones are the tests to be executed by Karma.

In any case, this should be it, there are more things you can do here, like setting which files you don’t want to be available using “exclude”, but let’s simplify the example.

Configuring RequireJS

Here is where my main headaches came, making Karma work was quite easy actually, but making this other one do its job properly was a pain in the ass, mainly because for some reason my defines didn’t work. At all. And my requires didn’t execute in the correct time, and as my tests were inside a require that waited until my files were loaded but karma was trying to execute the tests before that happened no tests were executed. And if I tried to load the tests into karma before the basic files were loaded, karma threw an error as it didn’t have the required library to execute this or that method (ie, “inject()” or “module()” require angular-mocks.js to be loaded).

That is why I ended up making sure that the unit testing libraries were loaded before hand by adding them to the Karma config file, then Karma would be able to read the test files without crashing and load the tests, and, once everything is ready, execute them. The key to all of this is the “deps” and “callback” properties on the requireJS config:

var tests = [];
for (var file in window.__karma__.files) {
    if (/Spec\.js$/.test(file)) {
        tests.push(file);
    }
}

require.config({
    baseUrl: '/base',

    paths: {
        'miniApp': 'MyProject/toTest1',
    },

    shim: {
        'miniApp': { deps: ['angular'] }
    },

    deps: tests,
    callback: window.__karma__.start
});

Trying to simplify as much as possible that config file to explain it: it first gets all the files that are going to be sent to the browser (not all the files loaded by the Karma server, but the ones with the “include:true”) into an array that is set as a dependency, meaning that requireJS won’t be ready to proceed until those files are loaded in the browser. Once they are, requireJS will execute its callback, which is a call to karma.start to run the tests.

But before running the tests Karma needs to load them, which is, they need to be loaded on the browser and executed, that will just “stack them” as tests. Once Karma.start is called, they will be run. And before loading the tests, we need angular-mocks to be there, which also requires angular.js to be there. I tried to set all those dependencies using the “shim” property of RequireJS, but didn’t succeed. Then I tried to add a define() on each test file, to set the dependencies, didn’t work either. I tried with a require(), nothing. And ended up doing this:

var tests = [];
for (var file in window.__karma__.files) {
    if (/Spec\.js$/.test(file)) {
        tests.push(file);
    }
}

require.config({
    baseUrl: '/base',

    paths: {
        'miniApp': 'MyProject/toTest1',
    },

    shim: {
        'miniApp': { deps: ['angular'] }
    },

    deps: tests
});

require(["miniApp"], function () {
    // Once all the libraries, project files and tests are loaded into Karma, we tell it to start testing them with this command:
    window.__karma__.start();
});

Notice that I have removed the callback from RequireJS, and instead, required my angular app to be loaded before executing the tests, as the tests and their required libraries were also sent to the browser in appropiate order using the Karma config file, everything works. I personally don’t fully like this approach, but it works… for now.

Conclusions

You may notice that I could have set up all the dependencies and files using RequireJS and its shim, then make a call to require my app and go on, but I had a problem: If I want to load all the tests files without writing one by one, only Karma can do that as RequireJS can’t read a computer’s directory. If those files are loaded before angular-mocks.js is, then the system crashes. If I set a requirejs([“angular-mocks”], function(){ … }); to load the tests once angular-mocks is ready, Karma won’t execute them as the callback is executed before angular-mocks is loaded, and the callback is what execs karma-start. If I were to wait until the tests were all loaded before executing karma.start that would work, the tricky point is how to do that as I don’t want to have to write the test files one by one and the callback is being executed once the files on “deps” are loaded.

Anyway, I may find a better solution in future once I know these technologies a bit better (learning Node.js + Karma + Mocha + Chai + Sinon and combine it with Angular and RequireJS… isn’t that easy) but for now, this is it. It works!!!

3 thoughts on “Configuring Karma to test Angular apps using RequireJS

  • Dheeraj Moudgil

    hey Ruben!
    Working on this since 1 month and i think i have finally found something related and relevant. I followed all your steps my tests and now they are working(earlier they were not even starting). But the main agenda for which I was doing all this(to load a js file in my tests) is not worked yet.
    As written, I have required “miniApp” in the end before calling “window.__karma__.start();”, In my tests I just console.log(miniApp.users); which is throwing me an error- miniApp is not defined.
    I don’t know what I am doing wrong. Please help me though this!
    n Thanks for the great tutorial!
    P.S: “miniApp” is the name of my JS file and “users” is a json object in it.

  • Hi Dheeraj, sorry I didn’t answer earlier but I don’t check my blog that often.

    The problem you have seems to be with Karma’s config file where you must set where your project files are so it knows who is “miniApp”, also, have a look at the requireJs config too, just in case.

    That said, this post was written nearly 2 years ago and things have evolved a bit in this time, instead of Mocha you should use Jasmine, and actually, there’s many new tools with Selenium and other frameworks to help with this nowadays I’m afraid. I didn’t have the chance to get into those as the projects where I’ve been lately didn’t have budget to do FrontEnd automated testing but as soon as I get into them I will write something in this blog 😉

Leave a Reply

Close Bitnami banner
Bitnami