I have started a project by using the Angular ASP.NET Core 2.0 template as found in Visual Studio 2017 Update 3 after installing the .NET Core 2.0 SDK.
Tests in Karma work fine, but I want to add code coverage to Karma. I have tried several different solutions, but nothing seems to work fully. The best I have come up with is that classes referenced from tests are covered (but not all typescript classes in the ClientApp), but the highlighting in the generated report is off and the messages don't make sense.
This is what I have done:
package.json
"devDependencies": {
"@types/chai": "4.0.1",
"@types/jasmine": "2.5.53",
"chai": "4.0.2",
"istanbul-instrumenter-loader": "^3.0.0",
"jasmine-core": "2.6.4",
"karma": "https://registry.npmjs.org/karma/-/karma-1.7.0.tgz",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage": "^1.1.1",
"karma-jasmine": "1.1.0",
"karma-remap-istanbul": "https://registry.npmjs.org/karma-remap-istanbul/-/karma-remap-istanbul-0.6.0.tgz",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "2.0.3"
}
Karma.conf.js:
module.exports = function (config) {
var webpackConfig = require('../../webpack.config.js')().filter(config => config.target !== 'node');
webpackConfig[0].devtool = 'inline-source-map';
config.set({
basePath: '.',
frameworks: ['jasmine'],
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],
preprocessors: {
'./boot-tests.ts': ['webpack', 'sourcemap']
},
reporters: ['progress', 'coverage', 'karma-remap-istanbul'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
mime: { 'application/javascript': ['ts','tsx'] },
singleRun: false,
webpack: webpackConfig, // Test against client bundle, because tests run in a browser
webpackMiddleware: { stats: 'errors-only' },
remapIstanbulReporter: {
reports: {
html: 'coverage'
}
}
});
};
webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const AotPlugin = require('@ngtools/webpack').AotPlugin;
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
module.exports = (env) => {
// Configuration in common to both client-side and server-side bundles
const isDevBuild = !(env && env.prod);
const tsUse = isDevBuild ? [
{
loader: 'istanbul-instrumenter-loader',
options: {
esModules: true
}
},
'awesome-typescript-loader?silent=true',
'angular2-template-loader'
] : '@ngtools/webpack';
const sharedConfig = {
stats: { modules: false },
context: __dirname,
resolve: { extensions: ['.js', '.ts'] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{
test: /\.ts$/, include: /ClientApp/,
use: tsUse
},
{ test: /\.html$/, use: 'html-loader?minimize=false' },
{
test: /\.css$/, use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize']
},
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot.browser.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'),
exclude: ['./**/*.server.ts']
})
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.module.server#AppModule'),
exclude: ['./**/*.browser.ts']
})
]),
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
I have been working on this lately, and after some digging I ended up with the below a solution.
NOTE: See the references at the bottom of this post to see what I found to eventually get to this solution. Enough of this is (necessarily) different from the source, so I don't think I should get into an trouble concerning copyrights.
First, I added these to the package.json for my solution, which was started using the new ASP.NET Core 2.0 Angular template that is available with Visual Studio 2017 (15.3 and up, I think). Add these to the
devDependencies
section of the package.json.Run
npm install
to add the bits into the application, or save the package.json and let VS do it's thing.Next, I changed the ClientApp/test/boot-tests.ts file.
Change this line:
const context = require.context('../app/', true, /\.spec\.ts$/);
to this:
const context = require.context('../app', true, /\.ts$/);
This (above) is directly from first reference below.
Next, I replaced the ClientApp/test/karma.conf.js content with this:
Now, when I run
npm test
from the root of the application, I get coverage reporting built in the ClientApp/test/coverage folder (open the index.html in a browser of you choice). I also addedtext-summary
to the reports options in the karma.conf.js, so you get a summary on the command line, too.You can change the
singleRun
in the karma.conf.js to false, if you want this to run and refresh as you make updates to code and unit tests. I ran into an issue where this when I tried all of this with the template after moving to Angular 5.0.0. With ng 5.0.0, a compile time warning is output, which is causing karma to recompile over and over, whether there are changes to code or not. I didn't have this issue with ng 4.2.5, which is the version the Core 2.0 template is using out of the box.QIQO.Core.Ng.Coverage is where I have a project that I have added these changes for you to look at if you like.
References:
Angular2SpaCodeCoverage from the JavaScriptServices wiki.
This will get you most of the way there, but the section with the changes to the karma.conf.js were not quite right, and didn't work with the latest version of the template, or webpack (and I am using 3.8.1 at the time I am writing this). It's usage of the
sourcemap-istanbul-instrumenter-loader
package didn't seem to work anymore either. Which leads me to the next reference.Issue: Code coverage for Angular 2 and webpack 2
It was the information in the last 2 posts in this issue thread that helped me wrap this one up. Thanks to Tdortiz!. Adding
options: { esModules: true }
to the karma.conf.js, and usingistanbul-instrumenter-loader
rather thansourcemap-istanbul-instrumenter-loader
were the tricks.