Slow Karma Unit Tests - reactjs

I wrote unit tests for a small reach application using Jasmine and Karma.And Karma is running slow for these tests.
This is my karma config:
var unitTestReportOutputDir = 'unit-test-report';
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
reporters: ['dots'],
port: 9876,
colors: false,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
autoWatchBatchDelay: 300,
exclude: ['./test/data/*.js'],
files: [
'tests.webpack.js'],
preprocessors: {
'tests.webpack.js': ['webpack']
},
webpack: require('./webpack.config.js'),
webpackMiddleware: {
noInfo: true
},
htmlReporter: {
outputDir: unitTestReportOutputDir, // where to put the reports
focusOnFailures: true, // reports show failures on start
namedFiles: true, // name files instead of creating sub-directories
pageTitle: 'Unit Test Report', // page title for reports; browser info by default
urlFriendlyName: false, // simply replaces spaces with _ for files/dirs
reportName: 'test-summary', // report summary filename; browser info by default
// experimental
preserveDescribeNesting: false, // folded suites stay folded
foldAll: false, // reports start folded (only with preserveDescribeNesting)
}
});
}
This is my webpack.config.js:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
var UglifyJsPlugin = require('uglify-js-plugin');
const path = require('path');
module.exports = {
devtool: 'inline-source-map',
entry: ['./src/index.js'],
output: {path: path.resolve(__dirname, "builds"), filename: 'bundle.js'},
module: {
rules: [
{
test: /\.jsx?$/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.sass$/,
exclude: '/node_modules/',
use: ExtractTextPlugin.extract({
fallback: "style-loader",
loader: "css-loader!sass-loader"
})
}
]
},
resolve: {
extensions: ['.js', '.jsx', '.sass']
},
plugins: [
new ExtractTextPlugin({
filename: "stylesheets/style.css",
disable: false,
allChunks: true
}),
new webpack.ProvidePlugin({
"$": "jquery",
"jQuery": "jquery",
"window.jQuery": "jquery"
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
})
]
};
What do you guys usually do to speed up Karma in running unit tests ?
I put the code at: https://github.com/zainulfranciscus/karma-test
karma took 1 min 15 sec to start and 33 seconds to ran the tests, whenever I updated a test. There are 35 unit tests
Thank you.

Check out the karma-parallel plugin here:
https://www.npmjs.com/package/karma-parallel
More popular and karma-webpack compatible compared to the karma-sharding plugin.

Have you looked at karma-sharding? This plugin addresses some memory usage problems with some browsers, and numerous or memory intensive specs.
It will not run your tests all in parallel, but as of version 4.0.0, it supports parallel browser execution(i.e. will shard tests across browsers listed in your karma.conf.js)

Related

How to run tests in Angular & AngularJs hybrid app?

As I want to migrate my AngularJs application to Angular 7, I created a hybrid app using ngUpgrade. The old AngularJs app is now embedded in the new Angular one.
When I run ng test it fires my Angular tests which is fine. But I also want to run the old tests written in AngularJs.
At the moment my Karma config looks like:
'use strict';
const webpackConfig = require('./karma.webpack.config');
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '#angular-devkit/build-angular', 'es6-shim'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('#angular-devkit/build-angular/plugins/karma'),
require('karma-es6-shim'),
require('karma-junit-reporter'),
require('karma-webpack'),
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'),
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
mime: {
'text/x-typescript': ['ts','tsx']
},
exclude: [
'**/*.html'
],
preprocessors: {
'./karma.entrypoint.ts': ['webpack']
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true,
stats: 'errors-only'
},
junitReporter : {
// results will be saved as $outputDir/$browserName.xml
outputDir : 'build/test-results/test/'
},
reporters: ['progress', 'kjhtml', 'junit'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
The ./karma.webpack.config file is:
const config = require('./webpack.config.js')();
const webpack = require('webpack');
const path = require('path');
const nibStylusPlugin = require('nib');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = Object.assign({}, config, {
context: path.resolve(__dirname, '.'),
entry: {
test: './karma.entrypoint.ts'
},
mode: 'development',
devtool: 'cheap-module-inline-source-map',
optimization: {
splitChunks: false,
runtimeChunk: false
},
plugins: [
new ForkTsCheckerWebpackPlugin(),
new webpack.ProvidePlugin({
"window.jQuery": "jquery",
tv4: "tv4"
}),
new webpack.LoaderOptionsPlugin({
options: {
stylus: {
use: [nibStylusPlugin()],
import: ['~nib/lib/nib/index.styl'],
}
}
})
]
});
And last but not least the karma.entrypoint.ts:
import 'jquery';
import 'angular';
import 'angular-mocks';
import * as moment from 'moment';
import{appConfigMock} from './karma.app-mock.config';
window['__APP_CONFIG__'] = appConfigMock;
(<any>moment).suppressDeprecationWarnings = true;
const testsContext = (<any>require).context('./src/app/', true, /\.spec\.ts$/);
testsContext.keys().forEach(testsContext);
Before, when I had only the AngularJs app, I run the tests with a very similar structure by using karma command. It utilized my webpack config to create a new karma webpack config. Now, when I run it through the ng test command, I even cannot require the './karma.webpack.config' at the beginning on karma configuration (I get Invalid config file! TypeError: require(...) is not a function).
How should I approach this case to run all the tests?
I finally solved it by running the tests separately but with one npm command, npm run test:
"test-ng1": "cross-env APP_CONFIG=testing NODE_ENV=development karma start --single-run",
"test-ng2-apps": "ng test --watch=false --ts-config=./karma.tsconfig.json",
"test": "yarn test-ng2-apps && yarn test-ng1",

Why my react app fails on production build after upgrading to react 16?

Why my react app fails on production build after upgrading to react 16 ?
After upgrading react to version 16 my app stoped working on production build, when running development works fine. If I downgrade to React 15.6 it still works fine on both prod and dev enviroments.
I am using: "webpack": "^3.5.6", and "react": "^16.0.0",
I am getting the following error:
Uncaught ReferenceError: require is not defined
My webpack prod configuration:
const path = require('path');
const merge = require("webpack-merge");
const webpack = require("webpack");
const config = require("./webpack.base.babel");
const OfflinePlugin = require('offline-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = merge(config, {
// devtool: "nosources-source-map",
devtool: "source-map",
// In production, we skip all hot-reloading stuff
entry: [
'babel-polyfill', // Needed for redux-saga es6 generator support
path.join(process.cwd(), 'src/client/app.js'), // Start with app.js
],
performance: {
assetFilter: (assetFilename) => !(/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename)),
},
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true
}),
new HtmlWebpackPlugin({
template: "src/client/index.html",
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
inject: true,
}),
// Shared code
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
children: true,
minChunks: 2,
async: true,
}),
// Avoid publishing files when compilation fails
new webpack.NoEmitOnErrorsPlugin(),
// Put it in the end to capture all the HtmlWebpackPlugin's
// assets manipulations and do leak its manipulations to HtmlWebpackPlugin
new OfflinePlugin({
relativePaths: false,
publicPath: '/',
// No need to cache .htaccess. See http://mxs.is/googmp,
// this is applied before any match in `caches` section
excludes: ['.htaccess'],
caches: {
main: [':rest:'],
// All chunks marked as `additional`, loaded after main section
// and do not prevent SW to install. Change to `optional` if
// do not want them to be preloaded at all (cached only when first loaded)
additional: ['*.chunk.js'],
},
// Removes warning for about `additional` section usage
safeToUseOptionalCaches: true,
AppCache: false,
}),
]
});
How can i fix it ?
webpack.base.babel.js
// Common Webpack configuration used by webpack.config.development and webpack.config.production
const path = require("path");
const webpack = require("webpack");
const autoprefixer = require("autoprefixer");
const e2c = require("electron-to-chromium");
const GLOBALS = require('../bin/helpers/globals');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const isProd = process.env.NODE_ENV === 'production';
const postcssLoaderOptions = {
plugins: [
autoprefixer({
browsers: e2c.electronToBrowserList("1.4")
}),
],
sourceMap: !isProd,
};
GLOBALS['process.env'].__CLIENT__ = true;
module.exports = {
target: 'web', // Make web variables accessible to webpack, e.g. window
output: {
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].[hash].chunk.js',
path: path.resolve(process.cwd(), 'build'),
publicPath: "/"
},
resolve: {
modules: ["node_modules"],
alias: {
client: path.resolve(process.cwd(), "src/client"),
shared: path.resolve(process.cwd(), "src/shared"),
server: path.resolve(process.cwd(), "src/server")
},
extensions: [".js", '.jsx', ".json", ".scss"],
mainFields: ["browser", "module", 'jsnext:main', "main"],
},
plugins: [
new webpack.NormalModuleReplacementPlugin(
/\/Bundles.js/,
'./AsyncBundles.js'
),
new webpack.IgnorePlugin(/vertx/),
new webpack.ProvidePlugin({
Promise: 'imports-loader?this=>global!exports-loader?global.Promise!es6-promise',
fetch: "imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch", // fetch API
$: "jquery",
jQuery: "jquery",
}),
new webpack.DefinePlugin(GLOBALS),
new ExtractTextPlugin({
filename: "css/[name].[hash].css",
disable: false,
allChunks: true
})
],
module: {
noParse: /\.min\.js$/,
rules: [
// JavaScript / ES6
{
test: /\.(js|jsx)?$/,
include: [
path.resolve(process.cwd(), "src/client"),
path.resolve(process.cwd(), "src/shared"),
],
exclude: /node_modules/,
use: "babel-loader"
},
// Json
{
test: /\.json$/,
use: 'json-loader',
},
//HTML
{
test: /\.html$/,
include: [
path.resolve(process.cwd(), "src/client"),
],
use: [
{
loader: "html-loader",
options: {
minimize: true
}
}
]
},
// Images
// Inline base64 URLs for <=8k images, direct URLs for the rest
{
test: /\.(png|jpg|jpeg|gif|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: "url-loader",
options: {
limit: 8192,
name: "images/[name].[ext]?[hash]"
}
}
},
// Fonts
{
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/font-woff'
}
}
},
{
test: /\.(ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: 'file-loader'
},
// Styles
{
test: /\.scss$/,
include: [
path.resolve(process.cwd(), "src/client"),
path.resolve(process.cwd(), "src/shared"),
],
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader",
options: {
sourceMap: true,
modules: true,
importLoaders: 1,
localIdentName: '[local]_[hash:base64:3]'
}
},
{
loader: "postcss-loader",
options: postcssLoaderOptions
},
{
loader: "sass-loader",
options: {
sourceMap: true,
outputStyle: "compressed"
}
}
]
})
},
]
}
};
The fix was rly simple.
I just needed to remove this line noParse: /\.min\.js/
Which does :
Prevent webpack from parsing any files matching the given regular
expression(s). Ignored files should not have calls to import, require,
define or any other importing mechanism.

Use of reserved word 'class' with karma and webpack

I'm building a project with angularJS in ES6.
I'm trying to config karma test runner and coverage with istanbul.
When I try to lauch unit test with Karma, I've following error :
Use of reserved word 'class'
This is my karma config file :
var path = require('path');
module.exports = function (config) {
config.set({
basePath: '',
plugins: [
'karma-coverage',
'karma-jasmine',
'karma-phantomjs-launcher',
'karma-webpack',
'karma-coverage-istanbul-reporter'
],
frameworks: ['jasmine'],
files: [
'tests/index.js'
],
preprocessors: {
'tests/index.js': 'webpack'
},
webpack: {
module: {
rules: [{
test : /\.js$/,
use : {
loader: 'istanbul-instrumenter-loader',
query : {
esModules: true
}
},
include: path.resolve('src/')
}, {
test: /\.html$/, loaders: ["html-loader"]
}, {
test: /\.less$/,
use : [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader"
}]
}, {
test : /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
loader: 'url-loader'
}]
}
},
reporters: ['progress', 'coverage-istanbul'],
coverageIstanbulReporter: {
reports : ['text-summary'],
fixWebpackSourcePaths: true
},
port : 9876,
colors : true,
logLevel : config.LOG_INFO,
autoWatch : true,
browsers : ['PhantomJS'],
singleRun : true,
concurrency: Infinity,
})
};
And this is my tests/index.js file
// requires all tests in `project/test/src/components/**/index.js`
const tests = require.context('./specs/', true, /index\.js$/);
tests.keys().forEach(tests);
// requires all components in `project/src/components/**/index.js`
const components = require.context('../src/', true, /\.js$/);
components.keys().forEach(components);
Did I miss something ?
Thanks.
PhantomJS currently doesn't support es2015+.
So you either need a transpiler loader like babel-loader,
or you need to install PhantomJS beta which supports new syntax.

babel webpack configuration ignored in karma.conf.js?

I am trying to make my first JS unit test with Karma and Jasmin. I am testing a react app.
I generated the karma config with "karma init" and modified it, see below for the karma.config.js
The webpack.config is required in the karma.config.js, but the babel loader is completely ignored, why?
I noticed it's ignored as it resulted in errors of undefined variable, etc...
When adding parts of the webpack.config.js directly in the karma.config.js (copy/paste), it works, but that is not what I want as I am duplicating code like my loaders and aliases, etc... How to solve this? See below also the webpack.config.js
The karma.config.js:
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: 'src/js/',
frameworks: ['jasmine'],
files: [
'tests/*.test.js',
],
preprocessors: {
'**/tests/*.test.js': ['webpack', 'sourcemap'],
},
webpack: require("../webpack.conf.js"),
webpackMiddleware: {
stats: "errors-only"
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['PhantomJS'],
phantomJsLauncher: {exitOnResourceError: true},
singleRun: false,
concurrency: Infinity
})
};
The webpack.config.js:
module.exports = function (env) {
if (env !== undefined) {
let analyse = !!env.analyse;
console.log("Analyse?: " + analyse);
if (analyse) {
plugins.push(new BundleAnalyzerPlugin({analyzerMode: 'static'}));
}
}
return {
entry: {
entry: ['./src/entry.js', './src/js/utils.js'],
},
devtool: devTool,
devServer: devServer,
output: {
path: __dirname + '/dist',
filename: '[name]-[hash].cache.js', // it contains the name ".cache", that is used in the webserver config.
sourceMapFilename: '[name]-[hash].cache.js.map',
},
module: {
rules: [
{ // The Babel loader:
test: /(\.jsx|\.js)$/,
exclude: /(node_modules|bower_components)/,
use: [{
loader: 'babel-loader',
options: {
presets: ['babel-preset-es2015', 'babel-preset-react'].map(require.resolve),
plugins: ['babel-plugin-transform-react-jsx-img-import'].map(require.resolve) // It will convert the used images to to "require" statements such that it's used by a loader below.
}
}]
},
{
test: /\.s?css$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(png|gif|jpe?g)$/,
use: [{
loader: 'file-loader',
options: {
name: 'resources/images/[name]-[hash]-cache.[ext]'
}
}]
},
{
test: /\.(otf|svg|eot|ttf|woff2?)(\?.*$|$)/,
use: [{
loader: 'file-loader',
options: {
name: 'resources/fonts/[name]-[hash]-cache.[ext]'
}
}]
},
]
},
plugins: plugins,
externals: ['axios'],
resolve: {
alias: {
// Ref: https://webpack.js.org/configuration/resolve/
Context: path.resolve(__dirname, 'src/js/context'),
Utils: path.resolve(__dirname, 'src/js/utils'),
....etc...
},
}
};
};
in karma.config.js:
webpack: require("../webpack.conf.js")
you're giving "webpack" a function instead of an object. you should immediately invoke it (with or without an env param) require("../webpack.conf.js")()

Production webpack increases size of bundle.js.map

Im trying to create a production build of my code and have made a webpack.production.config.js file. When I run 'webpack -p', my bundle.js file size is reduced but my bundle.js.map file size is increased. Here is the code and respective outputs:
webpack.config.js:
const path = require("path"); //eslint-disable-line
const webpack = require('webpack');
module.exports = {
context: __dirname,
entry: "./frontend/index.jsx",
output: {
path: path.join(__dirname, 'assets', 'build'),
filename: "bundle.js",
devtoolModuleFilenameTemplate: '[resourcePath]',
devtoolFallbackModuleFilenameTemplate: '[resourcePath]?[hash]'
},
externals: {
'cheerio': 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true
},
module: {
loaders: [
{
test: [/\.jsx?$/, /\.js?$/],
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader'
}
]
},
devtool: 'source-map',
eslint: {
configFile: './.eslintrc'
},
resolve: {
extensions: ["", ".js", ".jsx" ]
},
watchOptions: {
aggregateTimeout: 500,
poll: 2000,
ignored: /node_modules/
}
};
11:22 $ webpack
Hash: 3eaf0c4ed8964deb6866
Version: webpack 1.13.3
Time: 5805ms
Asset Size Chunks Chunk Names
bundle.js 2.16 MB 0 [emitted] main
bundle.js.map 2.53 MB 0 [emitted] main
+ 484 hidden modules
webpack.production.config.js:
const path = require("path"); //eslint-disable-line
const webpack = require('webpack');
module.exports = {
context: __dirname,
entry: "./frontend/index.jsx",
output: {
path: path.join(__dirname, 'assets', 'build'),
filename: "bundle.js",
devtoolModuleFilenameTemplate: '[resourcePath]',
devtoolFallbackModuleFilenameTemplate: '[resourcePath]?[hash]'
},
externals: {
'cheerio': 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true
},
module: {
loaders: [
{
test: [/\.jsx?$/, /\.js?$/],
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader'
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: true
}
})
],
devtool: 'cheap-module-source-map',
resolve: {
extensions: ["", ".js", ".jsx" ]
}
};
11:19 $ webpack -p
Hash: c40d4a49c049e8b5a525
Version: webpack 1.13.3
Time: 20990ms
Asset Size Chunks Chunk Names
bundle.js 805 kB 0 [emitted] main
bundle.js.map 5.55 MB 0 [emitted] main
+ 484 hidden modules
Any idea why bundle.js.map increases so significantly and if this is a problem? I also get a bunch of warnings from Uglify.js that I've been told to ignore.
Thanks.
I don't think this is a problem because source-maps are only used for debugging.
Also they are only loaded when devtools are open. Performance impact of using css / javascript source-maps in production
In short it is better to add several plugins (see description at the end) and use section "optimization: {...}" in your webpack.config.js
I have webpack 6.0.1. Based on the documentation I tested, use the following configuration ideas for webpack.config.js. You can test your configuration against these settings:
//webpack.config.js
module.exports = {
...
devtool: 'cheap-module-source-map',
...
plugins : [
...
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.HashedModuleIdsPlugin({
hashFunction: 'sha256',
hashDigest: 'hex',
hashDigestLength: 4
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
...
optimization: {
namedModules: false,
namedChunks: false,
nodeEnv: 'production',
flagIncludedChunks: true,
occurrenceOrder: true,
sideEffects: true,
usedExports: true,
concatenateModules: true,
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
}
},
minSize: 30000,
maxAsyncRequests: 5,
maxAsyncRequests: 3,
},
noEmitOnErrors: true,
minimize: true,
minimizer: [
// we specify a custom UglifyJsPlugin here to get source maps in production
new UglifyJsPlugin({
cache: true,
parallel: true,
uglifyOptions: {
compress: false,
ecma: 6,
mangle: true
},
sourceMap: true
})
],
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
},
...
}
Notes:
webpack.optimize.ModuleConcatenationPlugin() - concatenate the scope
of all your modules into one closure and allow for your code to have
a faster execution time in the browser
webpack.HashedModuleIdsPlugin() - cause hashes to be based on the
relative path of the module, generating a four character string as
the module id
webpack.optimize.OccurrenceOrderPlugin() - vary the
distribution of the ids to get the smallest id length for often used
ids
webpack.NoEmitOnErrorsPlugin() - skip the emitting phase
whenever there are errors while compiling. This ensures that no
assets are emitted that include errors

Resources