Adding code coverage to the ASP.NET Core Angular 2

2019-05-20 06:36发布

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];
};

1条回答
The star\"
2楼-- · 2019-05-20 06:57

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.

"karma-sourcemap-loader": "^0.3.7",
"karma-remap-istanbul": "^0.6.0",
"istanbul-instrumenter-loader": "^3.0.0"

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:

const path = require('path');

var webpackConfig = require('../../webpack.config.js')().filter(config => config.target !== 'node')[0];
webpackConfig.module.rules.push({
    test: /\.ts$/,
    include: [path.resolve(__dirname, '../app')],
    use: {
        loader: 'istanbul-instrumenter-loader?force-sourcemap=true',
        options: { esModules: true }
    },
    enforce: 'post',
    exclude: [/\.spec\.ts$/]
});

module.exports = function (config) {
    config.set({
        basePath: '.',
        frameworks: ['jasmine'],
        files: ['../../wwwroot/dist/vendor.js', './boot-tests.ts'],
        preprocessors: {
            './boot-tests.ts': ['webpack']
        },
        reporters: ['progress', 'karma-remap-istanbul'],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: true,
        browsers: ['Chrome'],
        mime: { 'application/javascript': ['ts', 'tsx'] },
        singleRun: true,
        webpack: webpackConfig,
        webpackMiddleware: { stats: 'errors-only' },
        remapIstanbulReporter: {
            reports: {
                html: 'ClientApp/test/coverage',
                'text-summary': null
            }
        }
    });
};

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 added text-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 using istanbul-instrumenter-loader rather than sourcemap-istanbul-instrumenter-loader were the tricks.

查看更多
登录 后发表回答