This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Angular with Webpack - Testing with Karma, Mocha, and Chai

10:22 Angular 1.x lesson by

Testing in Angular applications takes a bit of setup. Webpack simplifies things considerably. In this lesson you'll see how to test an Angular application built with Webpack using karma, mocha, and chai.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Testing in Angular applications takes a bit of setup. Webpack simplifies things considerably. In this lesson you'll see how to test an Angular application built with Webpack using karma, mocha, and chai.

Avatar
Tristan

I'm not too sold on the idea of pulling the tests in via the application instead of loading the application into the test, because the use of the ON_TEST switch means that you are forced to use require which feels wrong when you are building within es6 because you should be using import moduleName from 'module' and that is impossible to do within an if statement, as all imports are required to be at the root of the module (i.e. not within any extra scopes or blocks)

Avatar
Kent C.

I agree that it stinks that you can't put an import statement in a block, but because we're building the application with webpack there's no problem with using require and it is worth having the modularity of this approach even if it means having that little inconsistency. But we all have our opinions.

An alternative approach would be to have an index.test.js in the directory (side-by-side with the index.js file). That wouldn't be too bad. I hope everything else was helpful!

In reply to Tristan
Avatar
Kent C.

Of course, with this approach you would have to specify a different entry for your tests. This is no problem because the entry file is normally pretty small. And if it's not, you can create an index.common.js file that the index.js and index.test.js files import.

In reply to Kent C.
Avatar
Telepath

So glad you're doing this series. Really great so far. There is definitely still a bit of awkwardness involved in using webpack but overall I think it will end up being much cleaner than trying to do everything with gulp - anxious to see more!

Avatar
Kent C.

Interested to know what you would consider "awkwardness." I don't feel like it's awkward at all, but I am eager to know what other people feel about it as this will help direct my lessons.

In reply to Telepath
Avatar
Tristan

I'd have to agree that i'd feel happier doing it this way as opposed to having this weird side case for testing in it that only works within the context of require-able modules.

Also another thing to note is that with es6 modules, as opposed to node modules index.js is not a defaultly loaded script, it's better to point to a specific file so instead of import directives from './directives' you'd be better of doing something like import directives from './directives/common' where you'd have directives/common.js thus your files are compatable with the es6 loader without much System configuration

In reply to Kent C.
Avatar
Kent C.

Just a different way to do things. This lesson is how you do stuff with webpack and webpack enables everything that I did. I really like the api and the approach and I feel like it empowers me to build modular scaleable angular (not an easy thing to do).

In reply to Tristan
Avatar
moti zharfati

I am little bit confused, don't we need karma or gulp anymore? this method (of resolving files with webpack) is going to work the same with angular 2?
I saw a places who try to mix between gulp and webpack, is there a good reason to do that?

Avatar
Kent C.

Good question. I don't use grunt/gulp at all in my project at work. I do use grunt in angular-formly for deploying gh-pages (https://github.com/formly-js/angular-formly). Karma is still used for sure, and it uses the karma-webpack plugin to integrate with webpack. But to run it, we're still using karma start. So karma, in this example, is still very much useful. But I have found other build systems to not be totally useful because webpack just does everything for you from your JS to your CSS and even your images. They all go into the bundle.js file and you just deploy that.

There are some things that webpack WONT bundle. Like fonts or big images. For those, you can tell webpack where to put them. Also, you need to copy your index.html, robots.txt, and other non-bundled resources. In my project, I accomplish this as simply as:

cp app/{index.html,.htaccess,favicon.ico,robots.txt} dist/

and boom, I don't need an entire build config or pipeline just to do something I can do in a bash script.

As far as your question with Angular 2. It will absolutely work! Right now I'm trying to make an example, but Angular 2 is still pretty young so it's been a little bit of a struggle, but it's definitely going to work :-)

In reply to moti zharfati
Avatar
moti zharfati

Thank you!

In reply to Kent C.
Avatar
Kent C.

You're very welcome!

In reply to moti zharfati
Avatar
Jim

Great set of episodes. Kent, is it possible to have a future episode on how you can deploy such an app?

Avatar
Kent C.

Awesome idea. I'll add it to my list!

In reply to Jim
Avatar
Yonatan

Hi,
Great tutorial. Thanks.
I've tried to clone your code, but it gave me errors. I've downloaded the project, ran npm install, and then npm start - but it gave me this:
0 info it worked if it ends with ok
1 verbose cli [ 'c:\Program Files\nodejs\\node.exe',
1 verbose cli 'c:\Program Files\nodejs\nodemodules\npm\bin\npm-cli.js',
1 verbose cli 'start' ]
2 info using npm@2.5.1
3 info using node@v0.12.0
4 verbose node symlink c:\Program Files\nodejs\node.exe
5 verbose run-script [ 'prestart', 'start', 'poststart' ]
6 info prestart webpack-angular@1.0.0
7 info start webpack-angular@1.0.0
8 verbose unsafe-perm in lifecycle true
9 info webpack-angular@1.0.0 Failed to exec start script
10 verbose stack Error: webpack-angular@1.0.0 start: `node node
modules/.bin/webpack-dev-server --content-base app
10 verbose stack Exit status 1
10 verbose stack at EventEmitter.<anonymous> (c:\Program Files\nodejs\node_modules\npm\lib\utils\lifecycle.js:213:16)
10 verbose stack at EventEmitter.emit (events.js:110:17)
10 verbose stack at ChildProcess.<anonymous> (c:\Program Files\nodejs\node_modules\npm\lib\utils\spawn.js:14:12)
10 verbose stack at ChildProcess.emit (events.js:110:17)
10 verbose stack at maybeClose (child_process.js:1008:16)
10 verbose stack at Process.ChildProcess._handle.onexit (child_process.js:1080:5)
11 verbose pkgid webpack-angular@1.0.0
12 verbose cwd C:\Users\Yonk\Documents\webPackApp
13 error Windows_NT 6.1.7601
14 error argv "c:\\Program Files\\nodejs\\\\node.exe" "c:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "start"
15 error node v0.12.0
16 error npm v2.5.1
17 error code ELIFECYCLE
18 error webpack-angular@1.0.0 start:
node nodemodules/.bin/webpack-dev-server --content-base app`
18 error Exit status 1
19 error Failed at the webpack-angular@1.0.0 start script 'node node
modules/.bin/webpack-dev-server --content-base app'.
19 error This is most likely a problem with the webpack-angular package,
19 error not with npm itself.
19 error Tell the author that this fails on your system:
19 error node node_modules/.bin/webpack-dev-server --content-base app
19 error You can get their info via:
19 error npm owner ls webpack-angular
19 error There is likely additional logging output above.
20 verbose exit [ 1, true ]
Any idea why?

Avatar
Yonatan

I do have another question that's more controversial.
We're currently using requirejs as our AMD library.

In your series, you cover almost everything - from requiring files through bundeling to testing. It's great to know something covers (almost) everything before messing around with it (+1 for the deployment tutorial addition :)).
Joel wrote to me that requirejs is a bad practice with angular (even though many claim it's a good one, but I do find requirejs to be a bit... unexpected, especially when it comes to testing). Would you suggest moving from requirejs to webpack as a good practice (or at least a good move)?

Avatar
Kent C.

First off, about your error. I'm not certain what the problem is. Webpack has a strong community though and if you take that question to StackOverflow, you should receive an answer.

As for AMD, webpack actually can consume AMD modules very nicely. So even if you adopt webpack, you don't have to change all of your code immediately.

That said, AMD is dying in popularity. The BEST is to write your code using as much ES6 as possible.

Note: Webpack is a module loader, not a module system. It simply consumes modules. It can consume AMD, CommonJS, ES6 modules, and even globals.

Good luck!

In reply to Yonatan
Avatar
wil

The karma.config watch property does not work for me. Did it work for you?

Any time a file changed the test harness just plain crashed.

And, I cannot think of a less informative or enlightening test than:

expect(true).to.be.true;
Avatar
Kent C.

Hmm... Perhaps you could write-up a stack overflow question or file an issue on Karma's github?

In reply to wil
Avatar
CodeHeaven

Oh boy. Is unfortunate that less than 1 year later almost all courses I try to follow will run into problems that I just can't follow what's in the video, and usually into some specific problems that I just cannot solve.

Things move fast, I know, but this is being frustrating...

Avatar
Mark

it's mostly due to the node modules they use at the time, then go out of date pretty quickly. I've been looking at their module versions in their package.json's on github and see the version numbers. Then i try to find answers on upgrading those modules

i also am struggling with a lot of these tho :catface:

In reply to CodeHeaven
Avatar
Kent C.

I'm sorry about that. I've been thinking that I should probably update this series. The problem you're most likely running into is the version of babel you're using. This series uses Babel v4 and the latest version of Babel is v6. Version 6 was a hugely breaking change to Babel and how everything works. Try getting things going with Babel 5 and then see if you can then upgrade it to Babel 6 (you'll also need an older version of the babel-loader). Take a look at this guide to help you upgrade.

In reply to CodeHeaven
Avatar
Lee Blazek

Any chance of getting this going with the oc lazyload module?

Avatar
Kent C.

Unfortunately I never personally got that far in my experience and I'm not doing Angular at work anymore so it's unlikely that I'll learn much about ocLazyLoad. Good luck!

In reply to Lee Blazek
Avatar
Jonathan

Hi,

First of all, I wanted to say that I'm really loving this course.
Thanks so much!

However, this part really doesn't work for me.

I'm getting:

rejection Error: not a directory
```in
```MemoryFileSystem.js:144:10

I'm using jasmine instead of mocha & chai, and I have multiple modules (which my main module - "myApp" - depends on - i.e. "myApp.directives", "myApp.services", etc.).

I'm wondering if I'm the only one having problems.

I've got karma-webpack@^1.7.0 and webpack@^1.12.12

When I looked at the karma.conf.js file on the karma-webpack plugin github page, it looked entirely different. But that also didn't work for me.

So is anyone else having trouble? or even a solution (better :))?

Thanks!

Avatar
Kent C.

Hi Jonathan,
I think we'll need more info to help with that. I would recommend that you follow the Karma instructions for getting help: https://github.com/karma-runner/karma/blob/master/CONTRIBUTING.md#got-a-question-or-problem

Good luck!

In reply to Jonathan
Avatar
Cameron

Kent,

Thanks for the great tutorial. I used it to setup a similar build with typescript.
I am having trouble using karma-coverage (Istanbul). Have you used a coverage tool with this style of webpack angular before?

I have seen similar builds online with the test file separate from the index. I can get karma-coverage working, with a build like the one in the tutorial but it includes the whole project not just the files under test.

Avatar
Kent C.

Hi Cameron!
I'm glad you've found this series helpful! I've never used TypeScript before, but I'm confident that you should be able to configure karma-coverage in such a way to include only project files in your report. What have you tried?

In reply to Cameron
Avatar
Linghua

I was having this issue 12 03 2016 17:13:45.509:WARN [plugin]: Cannot find plugin "karma-chai".

And
npm i -g karma-cli

Solves it, maybe you want to mention it:)

Avatar
samir shah

Thanks you saved my day :)

In reply to Linghua
Avatar
janppires

Hi Kent.

What kind of setup is needed to have karma running all the tests and vendor code to be in a separated chunk?

I would like to have only one place were I define my vendor dependencies.
We can declare our dependencies as an array in webpack config (entry.vendor : ['angular-resource']). But then Karma complains that does not recognize my dependencies. That happens because I do not declare my vendor dependencies inside entry.app file.

I have been watching also your other course 'Using Webpack for Production JavaScript Applications' but it does not help me, since I am trying to apply the angular structure you define here.

I would love if you could add a video in this series about 'angular+vendor_chuncks+karma' would be awesome.

Thanks!

Avatar
Kent C.

Hi janppires! In this lesson I explain that you should not use the CommonsChunkPlugin in a test environment. When you say:

I do not declare my vendor dependencies inside entry.app file.

Do you mean that you don't explicitly require/import those dependencies in your app's entry file? If not, you should. By using the CommonsChunkPlugin for the vendor entry, those dependencies wont be bundled in the app bundle.

In reply to janppires
Avatar
janppires

Hi Kent.

I managed to have it working like this:
Only for production I setup a vendor entry file
entry: {
vendor : './src/main/webapp/app/vendor.js'
}

and using the ONTEST trick, i declare on my main index.js file:
if(ON
TEST) {
require('./vendor');
}

So with this, when I am distributing, I bundle a vendor file and use also the CommonsChunckPlugin. When I am testing I include everything (with ON_TEST) but do not use CommonsChunckPlugin, because of the reason you mention. And it works OK. I still do not like the solution, but at least I have only one place where I define my vendor dependencies (vendor.js file).

In reply to Kent C.
Avatar
janppires

Hi again!

With the structure you define for this angular project, how do you add code coverage?

I cannot apply the explanation you give "Using Webpack for Production JavaScript Applications (Add Code Coverage to tests in a Webpack project)".
In this angular project we are using the "alternative usage" of karma-webpack, right? How do I tell to karma which files it should do code coverage?

As it should looks familiar to you, I have this:

var path = require('path');
var webpackConfig = require('./webpack.config');
var entry = path.resolve(webpackConfig.entry.bundle);
var preprocessors = {};
preprocessors[entry] = ['webpack', 'sourcemap'];

module.exports = function(config) {
  config.set({
      browsers: ['PhantomJS'],
      frameworks: ['mocha', 'chai-jquery', 'jquery-2.1.0', 'sinon-chai'],
      reporters: ['mocha', 'coverage'],
      coverageReporter : {
          dir: 'build/reports/web-coverage',
          reporters: [
              {type: 'lcov', subdir: '.'},
              {type: 'json', subdir: '.'},
              {type: 'text-summary'}
          ]
      },
      logLevel: config.LOG_INFO,
      autoWatch: true,
      singleRun: true,
      colors: true,
      port: 9876,
      basePath: '',
      files: [entry],
      exclude: [],
      webpack : webpackConfig,
      preprocessors: preprocessors,
      concurrency: Infinity,
      webpackMiddleware: {noInfo: true},

      // omit plugins to allow automatic inclusion
      // plugins :[]
  })
}

I also was watching this video from you https://www.youtube.com/watch?v=P-1ZZkpEmQA but again, it is not angular, and I don't know what am I missing here! =(

Many thanks!

In reply to Kent C.
Avatar
janppires

Hi Kent,

I have created an example of my angular app, based on your proposal. But I cannot figure out how should I configure the karma.config file and how should I setup my spec files properly to accept code coverage!

https://github.com/janppires/angular-webpack-demo-project

I am struggling with which files should I preprocess, which files should I add to 'files' array, if karma-coverage already handles with istanbul or if I must add the preloader for it.

I would appreciate your help here!

Many thanks!

In reply to Kent C.
Avatar
janppires

Cool!!

Thanks a lot!

Cheers!

In reply to Kent C.
Avatar
Justin

Can you post an example of how to tests a service once you have things set up? I have everything setup correctly but I'm not sure how to write real tests.

Avatar
Kent C.

My favorite example of Angular testing is: https://github.com/zanthrash/yawa

In reply to Justin
Avatar
Chris Gaona

I've followed your tutorial and for the most part everything seems to be working, but for some reason when I run the karma test, it gives an error saying the following: SyntaxError: Use of reserved word 'import'. I'm trying to figure out what I'm doing wrong. I've been struggling to fix this error for a while now. Any idea why it would be giving this error? Here is my github repo if you have time to take a look: https://github.com/chris-gaona/chris-gaona-portfolio. Thank you!

Avatar
Kent C.

Hi Chris, based on that error, it sounds like you're ES6 modules aren't being transpiled. Things have changed a bit since I made this course and I think that you'd actually really benefit by going through my Frontend Masters workshop slides (specifically starting here) (the recording isn't available yet). But the slides (and corresponding links/diffs) should be really helpful to you. Good luck!

In reply to Chris Gaona
Avatar
Suhas

Thanks, I was struggling with the same issue.

In reply to Linghua

Testing with webpack in Angular takes a lot of setup, but once it's all set up, it's actually very easy and nice to work with. The first thing that we're going to need are a lot of dependencies.

We're going to npm install dev dependencies. First, we're going to use the karma runner, so I'll use karma. We'll also use karma-chrome-launcher, because we're going to use chrome. Then we're going to use karma-webpack. We'll also be testing with mocha and chai. That's all that we're going to need for testing in our Angular application.

Now let's go ahead and initialize our karma config, so karma init. We're going to be testing with mocha. We are not going to be using require. We are using chrome. We are not going to be specifying any files or excludes files. We'll do that a little bit differently. We will just go ahead and let it watch.

Let's open this up. This is pretty standard. We're going to add a couple of things here. We'll say plugins, and we're going to require the webpack, or rather karma-webpack plugin. Then we're also going to use the karma-chai and karma-mocha and karma-chrome launcher plugins. That's simple enough.

Now we're going to want to tell karma what files to load. With webpack, things are a little bit different in that, what's loaded into the browser is a bundle that's generated by webpack. Because we're using the karma webpack plugin, we're actually just going to specify the entry for our webpack config.

The way that we get that is we'll just get our webpack.config straight from webpack.config.js. We'll say require webpackConfig. This will come from webpack.config. We're going to get our entry, and this will actually come from the webpack.config, but we're going to need the path, module path, path.

Because if we look at our webpack config, we have our entry, but this is in respect to the context, so we need to resolve those two together. We're going to say path.resolve, webpackConfig.context, and webpackConfig.entry. That is our entry file, and that is the file that we're going to be telling karma to load.

We also need to specify our webpackConfig, and so we'll just give it our webpackConfig there. Then finally the preprocessors is one other thing that we need to specify, and this is very important. We'll say var preprocessors is an object, and then preprocessors for the entry file will be webpack.

That is our karma config. We have our plugins. We specify our preprocessors as an entry for webpack. We also specify the files as being our entry, and then we specify our webpackConfig as being the same as the webpackConfig here.

There's still yet more to do. One thing that in an Angular application that you need to do is you need to have the angular-mocks so that you can load the correct module and you have some other helpers there. We'll npm install, save as a dev dependency, angular-mocks. That installs.

Now we need to actually include that. Because we're just using our index.js, we need to include it in here. What you would normally, if you were just thinking about this, you would just say require angular-mocks/angular-mocks.

The problem that you have here is now you're loading angular-mocks into your production code. If so, if you were to deploy this, angular-mocks would run, and that would be a problem from a couple of standpoints.

What you really need is to have something like this. Now this will only be loaded into the browser if we're ON_TEST, and webpack is smart enough to know that if this code will never run, that it doesn't actually resolve that require.

What we need to figure out is how to get webpack to say, "Hey, this value is false in certain contexts." That is very easy to do using the define plugin. In our webpackConfig we're going to get webpack, and we're going to add plugins. It's an array, and we'll say a new webpack.DefinePlugin. This is an object of all of the variables that we want to have resolved to actual values in our code.

We have this ONTEST, and we're going to say process.env.NODEENV is equal to test. In the case that that is true, this will be evaluated to true, and this require will evaluate. In the case that that's false, this require will be ignored and [indecipherable 05:03] when we ship it to production we'll actually just totally remove all of this code anyway, so that's not a problem.

Now we're loading angular-mocks, but we don't have any tests to run yet, so let's go ahead in our directives. We're going to test this kcd-hello directive. kcd-hello.test.js. In here, we're going to describe kcd-hello, and...

One thing that's a little bit interesting is part of what we're trying to accomplish here is we never want to use the angular.module getter. That encourages modularity, and so we can move things around without having to worry about the names of modules or anything like that.

If we follow what we're doing currently where we have this directives with the index. It's a function that accepts a module and then it passes that onto the kcd-hello directive.

What we want to do, the API we're looking for is something like this. If ON_TEST, then we want to require the kcd-hello.test. This is modular because anytime we move these files, we want them to stay together so they're together. That keeps things a lot cleaner and easier to reason about. What we're looking for is something that we can pass the ngModule into so that it knows what module to register for the test.

Let's go ahead in our kcd-hello and we're going to take this and create an export default ngModule. This is a function that takes in ngModule. Then we'll use that to describe our test. Before each, we're going to say module, ngModule.name.

There's a problem here. In common JS land, where we're living now, module actually has meaning. We need to specify this on window.module so that we're referencing the module that angular-mocks creates. Now we can give our it. It should test properly. Then well just give ourselves a failing test. We'll say expect false to be true.

If everything works properly, what we need to do in our console when we actually run these tests is we'll say NODE_ENV=test. Then we'll say karma start. If everything runs properly, then we'll get a failing test. There we go, expect is not defined. I believe that I know the reason for that. If we go to karmaConfig, we need to specify our frameworks as chai so that we get expect on the global.

Let's restart this. We should get a failing test now. Expected false to be true. If we go back to our hello.test, expected true to be true. Now we have a succeeding test. That's how you test everything. Everything's all set up for this to work and it scales very well.

In review, you take your karmaConfig and you alter it in a few small ways beyond what you would normally do in a regular karma configuration. You need to determine your entry via your webpack context and entry. Then you assign the preprocessors property of that entry to the webpack.

Then as the files that are loaded into the browser is entry. You also specify the webpackConfig in your karmaConfig. That's pretty much all that's different from a regular karmaConfig that you would have in any other Angular application.

Before any of your tests are registered, you need to load in angular-mocks, because they depend on the window.module. I'm doing it this right after we require Angular in our entry file, and then we create our module, and then we require all the directives, and in the future other things that we need for this module.

We pass in the module, and when we're ON_TEST, we're going to require the test where we pass the module and that module is used to load the module before each test. From there, everything works as you would expect from a regular Angular test where you use the compile and whatever else you need to test these functions and these directives. That's how you set up tests with webpack.

Sorry, one other thing. Because I'm not a fan of typing all of that in your package.json, it's a good idea to have a script for test. We can just say NODE_ENV=test, and then karma start. Now in here I can say npm run test. That will start my test for me, and I don't need to worry about the proper options or anything else I need to pass in. That's webpack with Angular and karma testing.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?