unit-testing - 在 Angular 2.1.0 Webpack 上设置单元测试代码覆盖率

标签 unit-testing angular

我正在关注 this tutorial关于设置 Webpack Angular 2 项目。

我可以通过设置很好地运行单元测试,但我尝试使用 karma-coverage 和 remap-istanbul 向项目添加代码覆盖,但 karma-coverage 似乎没有在 coverage-final 中输出任何内容。 JSON.

我需要向 karma 配置中添加什么才能使测试配置正常工作?

这是我当前的配置:

var webpackConfig = require('./webpack.test');

module.exports = function (config) {
    var _config = {
        basePath: '',

        frameworks: ['jasmine'],

        files: [
          {pattern: './config/karma-test-shim.js', watched: false}
        ],

        preprocessors: {
            './config/karma-test-shim.js': ['webpack', 'sourcemap']
        },

        webpack: webpackConfig,

        webpackMiddleware: {
            stats: 'errors-only'
        },

        webpackServer: {
            noInfo: true
        },

        reporters: ['progress'],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: false,
        browsers: ['PhantomJS'],
        singleRun: true
    };

    config.set(_config);
};

最佳答案

您有两个选择,最简单的方法是使用 angular-cli .最困难的方法是根据该教程进行代码覆盖所需的更改,其中很多。您将被迫更改为 Webpack 2 的主要事情之一,我无法使用 Webpack 1 使 awesome-typescript-loader 与 karma 一起工作。代码覆盖率始终为空。我从 angular-cli 得到了一些灵​​感来自angular2-webpack-starter以下是更改:

karma.conf.js: 添加:

remapIstanbulReporter: {
  reports: {
    html: 'coverage',
    lcovonly: './coverage/coverage.lcov'
  }
},

然后改变这个:

reporters: ['progress'],    

为此:

reporters: ['progress', 'karma-remap-istanbul'],

webpack 配置有很多变化,所以我将粘贴整个配置文件,这样更容易:

webpack.common.js:

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');

module.exports = {
  entry: {
    'polyfills': './src/polyfills.ts',
    'vendor': './src/vendor.ts',
    'app': './src/main.ts'
  },

  resolve: {
    extensions: ['.ts', '.js']
  },

  module: {
    rules: [
      {
        test: /\.ts$/,
        loaders: ['awesome-typescript-loader', 'angular2-template-loader'],
        exclude: [/\.(spec|e2e)\.ts$/]
      },
      {
        test: /\.html$/,
        loader: 'html'
      },
      {
        test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
        loader: 'file?name=assets/[name].[hash].[ext]'
      },
      {
        test: /\.css$/,
        exclude: helpers.root('src', 'app'),
        loader: ExtractTextPlugin.extract({
          fallbackLoader: 'style-loader',
          loader: 'css-loader'
        })
      },
      {
        test: /\.css$/,
        include: helpers.root('src', 'app'),
        loader: 'raw'
      }
    ]
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      // Optimizing ensures loading order in index.html
      name: ['polyfills', 'vendor', 'app'].reverse()
    }),
    new webpack.optimize.CommonsChunkPlugin({
      minChunks: Infinity,
      name: 'inline',
      filename: 'inline.js',
      sourceMapFilename: 'inline.map'
    }),

    new HtmlWebpackPlugin({
      template: 'src/index.html'
    })
  ]
};

webpack.dev.js

var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

module.exports = webpackMerge(commonConfig, {
  devtool: 'cheap-module-eval-source-map',

  output: {
    path: helpers.root('dist'),
    filename: '[name].js',
    chunkFilename: '[id].chunk.js',
    sourceMapFilename: '[name].map',
    library: 'ac_[name]',
    libraryTarget: 'var'
  },

  plugins: [
    new webpack.LoaderOptionsPlugin({
      options: {
        tslint: {
          emitErrors: false,
          failOnHint: false,
          resourcePath: 'src'
        },
      }
    }),
    new ExtractTextPlugin('[name].css')
  ],

  devServer: {
    historyApiFallback: true,
    stats: 'minimal',
    watchOptions: {
      aggregateTimeout: 300,
      poll: 1000
    },
    outputPath: helpers.root('dist')
  },
  node: {
    global: true,
    crypto: 'empty',
    process: true,
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
});

webpack.prod.js:

var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var WebpackMd5Hash = require('webpack-md5-hash');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

module.exports = webpackMerge(commonConfig, {
  devtool: 'source-map',

  output: {
    path: helpers.root('dist'),
    filename: '[name].[chunkhash].js',
    sourceMapFilename: '[name].[chunkhash].bundle.map',
    chunkFilename: '[id].[chunkhash].chunk.js'
  },

  plugins: [
    new WebpackMd5Hash(),
    new webpack.NoErrorsPlugin(),
    new webpack.optimize.UglifyJsPlugin({
        mangle: { screw_ie8: true },
        compress: { screw_ie8: true }
    }),
    new ExtractTextPlugin('[name].[hash].css'),
    new webpack.DefinePlugin({
      'process.env': {
        'ENV': JSON.stringify(ENV)
      }
    }),
    new webpack.LoaderOptionsPlugin({
      options: {
        tslint: {
          emitErrors: true,
          failOnHint: true,
          resourcePath: helpers.root('src')
        },
        htmlLoader: {
          minimize: true,
          removeAttributeQuotes: false,
          caseSensitive: true,
          customAttrSurround: [
            [/#/, /(?:)/],
            [/\*/, /(?:)/],
            [/\[?\(?/, /(?:)/]
          ],
          customAttrAssign: [/\)?\]?=/]
        }
      }
    }),
    new webpack.ContextReplacementPlugin(
      /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
      helpers.root('src')
    )
  ],
  node: {
    fs: 'empty',
    global: true,
    crypto: 'empty',
    process: true,
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
});

webpack.test.js:

var helpers = require('./helpers');
var path = require('path');
var atl = require('awesome-typescript-loader');
var webpack = require('webpack');

module.exports = {
  devtool: 'inline-source-map',
  context: path.resolve(__dirname, './'),
  resolve: {
    extensions: ['.ts', '.js'],
    plugins: [
      new atl.TsConfigPathsPlugin({
        tsconfig: helpers.root('tsconfig.json')
      })
    ]
  },
  entry: {
    test: helpers.root('config/karma-test-shim')
  },
  output: {
    path: './dist.test',
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        enforce: 'pre',
        loader: 'tslint-loader',
        exclude: [
          helpers.root('node_modules')
        ]
      },
      {
        test: /\.js$/,
        enforce: 'pre',
        loader: 'source-map-loader',
        exclude: [
          helpers.root('node_modules/rxjs'),
          helpers.root('node_modules/@angular')
        ]
      },
      {
        test: /\.ts$/,
        loaders: [
          {
            loader: 'awesome-typescript-loader',
            query: {
              tsconfig: helpers.root('tsconfig.json'),
              module: 'commonjs',
              target: 'es5',
              useForkChecker: true
            }
          },
          {
            loader: 'angular2-template-loader'
          }
        ],
        exclude: [/\.e2e\.ts$/]
      },
      {
        test: /\.html$/,
        loader: 'html'

      },
      {
        test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
        loader: 'null'
      },
      {
        test: /\.css$/,
        exclude: helpers.root('src', 'app'),
        loader: 'null'
      },
      {
        test: /\.css$/,
        include: helpers.root('src', 'app'),
        loader: 'raw'
      },
      {
        test: /\.(js|ts)$/, loader: 'sourcemap-istanbul-instrumenter-loader',
        enforce: 'post',
        exclude: [
          /\.(e2e|spec)\.ts$/,
          /node_modules/
        ],
        query: { 'force-sourcemap': true }
      },
    ]
  },
  plugins: [
    new webpack.SourceMapDevToolPlugin({
      filename: null, // if no value is provided the sourcemap is inlined
      test: /\.(ts|js)($|\?)/i // process .js and .ts files only
    }),
    new webpack.LoaderOptionsPlugin({
      options: {
        tslint: {
          emitErrors: false,
          failOnHint: false,
          resourcePath: `./src`
        }
      }
    }),
    new webpack.ContextReplacementPlugin(
      /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
      helpers.root('src')
    )
  ],
  node: {
    fs: 'empty',
    global: true,
    process: false,
    crypto: 'empty',
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
}

package.json:
您将需要安装新的软件包并将您的启动脚本更新为:

"start": "webpack-dev-server --config config/webpack.dev.js --profile --watch --content-base src/",

并安装这些包:

npm i -D extract-text-webpack-plugin@2.0.0-beta.4 karma-remap-istanbul source-map-loader sourcemap-istanbul-instrumenter-loader tslint tslint-loader webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.3 webpack-md5-hash

最后但同样重要的是,我们只需要对 tsconfig.json 进行一些更改,因为我们现在使用的是 tslint,所以我们添加了一个 tslint.json 文件。

tsconfig.json:

{
  "compilerOptions": {
    "buildOnSave": false,
    "compileOnSave": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "dist/out-tsc",
    "noImplicitAny": true,
    "removeComments": false,
    "sourceMap": true,
    "suppressImplicitAnyIndexErrors": true,
    "target": "es5"
  }
}

tslint.json:

{
  "rules": {
    "member-access": false,
    "member-ordering": [
      true,
      "public-before-private",
      "static-before-instance",
      "variables-before-functions"
    ],
    "no-any": false,
    "no-inferrable-types": false,
    "no-internal-module": true,
    "no-var-requires": false,
    "typedef": false,
    "typedef-whitespace": [
      true,
      {
        "call-signature": "nospace",
        "index-signature": "nospace",
        "parameter": "nospace",
        "property-declaration": "nospace",
        "variable-declaration": "nospace"
      },
      {
        "call-signature": "space",
        "index-signature": "space",
        "parameter": "space",
        "property-declaration": "space",
        "variable-declaration": "space"
      }
    ],

    "ban": false,
    "curly": false,
    "forin": true,
    "label-position": true,
    "label-undefined": true,
    "no-arg": true,
    "no-bitwise": true,
    "no-conditional-assignment": true,
    "no-console": [
      true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-variable": true,
    "no-empty": false,
    "no-eval": true,
    "no-null-keyword": false,
    "no-shadowed-variable": true,
    "no-string-literal": false,
    "no-switch-case-fall-through": true,
    "no-unreachable": true,
    "no-unused-expression": true,
    "no-unused-variable": false,
    "no-use-before-declare": true,
    "no-var-keyword": true,
    "radix": true,
    "switch-default": true,
    "triple-equals": [
      true,
      "allow-null-check"
    ],
    "use-strict": [
      true,
      "check-module"
    ],

    "eofline": true,
    "indent": [
      true,
      "spaces"
    ],
    "max-line-length": [
      true,
      100
    ],
    "no-require-imports": false,
    "no-trailing-whitespace": true,
    "object-literal-sort-keys": false,
    "trailing-comma": [
      true,
      {
        "multiline": false,
        "singleline": "never"
      }
    ],

    "align": false,
    "class-name": true,
    "comment-format": [
      true,
      "check-space"
    ],
    "interface-name": false,
    "jsdoc-format": true,
    "no-consecutive-blank-lines": false,
    "no-constructor-vars": false,
    "one-line": [
      true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-finally",
      "check-whitespace"
    ],
    "quotemark": [
      true,
      "single",
      "avoid-escape"
    ],
    "semicolon": [true, "always"],
    "variable-name": [
      true,
      "check-format",
      "allow-leading-underscore",
      "ban-keywords"
    ],
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type"
    ]
  }
}

如果您愿意,可以检查 Angular.io 设置(左侧)和我为使覆盖有效而所做的更改(右侧)之间的差异 here

关于unit-testing - 在 Angular 2.1.0 Webpack 上设置单元测试代码覆盖率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40182067/

相关文章:

python - Python 的自动模拟?

angular - Angular2中的多个过滤条件

尽管模型发生变化,Angular4 PrimeNG DataTable 仍未更新

javascript - 即使函数存在,Angular 也会抛出错误

angular - Prime NG 的可编辑表格上的奇怪行为

ruby-on-rails - Rails 4 测试用户初始化总是空白

Java 单元测试 : how to measure memory footprint for method call

Python Unittest-类变量

objective-c - 在 ocUnit 中比较 NSArray

angular - 使用解析器 Angular 6 进行重定向