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 ON_TEST, and we're going to say process.env.NODE_ENV 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 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.