10

There is a ton of information on how to unit test with Webpack and Jasmine for Angular projects.

But I have a project that uses 'plain' typescript, not AngularJs. So I have ts classes, but I don't use components. I cannot figure out how to apply the information I'm finding to a non-AngularJs project; everything seems geared toward using components.

How do I integrate Jasmine (ts spec files) in a Typescript Webpack project?

I'd prefer a solution that uses a separate webpack config for the tests.

My setup/what I have to work with:

package.json Start script launches node with dev-build

{
  "name": "webpack.typescript",
  "version": "1.0.0",
  "description": "Webpack + TypeScript",
  "main": "dev-build.js",
  "author": "Shane Osbourne and John Lindquist",
  "license": "MIT",
  "scripts": {
    "start": "node dev-build"
  },
  "dependencies": {
    "bootstrap": "^3.3.7",
    "lodash": "^4.17.4"
  },
  "devDependencies": {
    "@types/lodash": "^4.14.53",
    "browser-sync": "^2.18.8",
    "bs-pretty-message": "^1.0.8",
    "css-loader": "^0.26.2",
    "node-sass": "^4.5.0",
    "sass-loader": "^6.0.2",
    "style-loader": "^0.13.2",
    "ts-loader": "^2.0.1",
    "typescript": "^2.2.1",
    "url-loader": "^0.5.8",
    "webpack": "^2.2.1",
    "webpack-dev-middleware": "^1.10.1"
  }
}

dev-build.js Load config, bundle, and start BrowserSync.

/**
 * Require Browsersync along with webpack and middleware for it
 */
var browserSync          = require('browser-sync').create();
var webpack              = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');

/**
 * Require ./dev-webpack.config.js and make a bundler from it
 */
var webpackConfig = require('./dev-webpack.config');
var bundler       = webpack(webpackConfig);

/**
 * Reload all devices when bundle is complete
 * or send a fullscreen error message to the browser instead
 */
bundler.plugin('done', function (stats) {
    if (stats.hasErrors() || stats.hasWarnings()) {
        return browserSync.sockets.emit('fullscreen:message', {
            title: "Error",
            timeout: 100000
        });
    }
    browserSync.reload();
});

/**
 * Run Browsersync and use middleware for Hot Module Replacement
 */
browserSync.init({
    server: 'app',
    open: false,
    logFileChanges: false,
    middleware: [
        webpackDevMiddleware(bundler, {
            publicPath: webpackConfig.output.publicPath,
            stats: {colors: true}
        })
    ],
    plugins: ['bs-pretty-message'],
    files: [
        'app/css/*.css',
        'app/*.html'
    ]
});

dev-webpack.config.js Handle Typescript and scss. (I was thinking I should have a test-webpack.config.js)

var webpack = require('webpack');
var path = require('path');

module.exports = {
  devtool: '#inline-source-map',

  entry: [
    './src/main.ts',
    './src/main.scss'
  ],

  output: {
    path: path.join(__dirname, 'app'),
    publicPath: '/',
    filename: 'dist/bundle.js'
  },

  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.LoaderOptionsPlugin({
      debug: true
    })
  ],

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

  module: {
    rules: [
      {
        test: /\.ts$/, use: [{
          loader: 'ts-loader'
        }]
      },
      {
        test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
        use: [{
          loader: 'url-loader'
        }]
      },
      {
        test: /\.scss$/,
        use: [{
          loader: "style-loader"
        }, {
          loader: "css-loader", options: {
            sourceMap: true
          }
        }, {
          loader: "sass-loader", options: {
            sourceMap: true
          }
        }]
      }
    ],
  }
};

What I've found:

https://dzone.com/articles/unit-testing-with-webpack-amp-mocha

Unit testing with Webpack, Jasmine (-core), typescript

Jasmine Spec as Typescript File

TypeScript compilation failure and Karma test execution?

Executing Typescript Jasmine tests via Webpack (Terminal) to test Angular2

2 Answers 2

5
+250

Ngz answer still included angular and al lot of extra things. So I'll try to answer with the bare necessities.

The test-webpack-config.js has the tests as entry point, since webpack looks from there for dependencies, and the ts-loader does the transpiling:

var webpack = require('webpack');
var path = require('path');

module.exports = {
  devtool: '#inline-source-map',

  entry: [
    './test/index.spec.ts',
  ],

  output: {
    filename: 'dist/bundle.js'
  },

  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.LoaderOptionsPlugin({
      debug: true
    })
  ],

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

  module: {
    rules: [
      {
        test: /\.ts$/, use: [{
          loader: 'ts-loader'
        }]
      }]
  }
};

An I used karma as a test runner, with the karma-webpack plugin, here is the config:

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

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser
    files: [
      'test/*.spec.ts',
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
       'test/**/*.spec.ts': ['webpack'],
       'src/**/*.ts': ['webpack'],
    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: true,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity, 

    mime: {
      'text/x-typescript': ['ts','tsx']
    },

    // Set Webpack configuration, but set the entry to spec files
    webpack: {
      module: webpackConfig.module,
      resolve: webpackConfig.resolve
    }
  })
}

The mime part is the key for having tests made in typescript to work.

For the full example: https://github.com/SierraNL/webpack-typescript-jasmine

Sign up to request clarification or add additional context in comments.

Comments

4

You need to setup a test runner and configure it to use a slightly different Webpack config for your tests. For this example, I've used Karma and the karma-webpack plugin.

You need to create a separate entry file for your test bundle and reference it in the value of the files and preprocessors properties of your karma config file:

// /test/karma.conf.js
const argv = require('yargs').argv;
const isDebug = argv.debug === 'true';
const isCoverage = argv.coverage === 'true';
const path = require('path');
const webpack = require('webpack');

// Optional instrumentation loader for coverage reporting
const instrumentLoader = {
    test: /\.js$|\.ts$/,
    enforce: 'post',
    use: [
        {
            loader: 'istanbul-instrumenter-loader',
            options: {
                esModules: true
            }
        }
    ],
    exclude: /node_modules/
};

const coverageReporter = {
    reporters: [
        {
            type: 'text-summary'
        }
    ]
}

module.exports = (config) => {
    config.set(Object.assign({
            browsers: [isDebug ? 'Chrome' : 'PhantomJS'],
            files: [
                // test bundle entry file
                './test.entry.js'
            ],
            frameworks: ['jasmine', 'es6-shim'],
            reporters: isDebug ? ['spec'] : ['spec', 'coverage'],
            singleRun: !isDebug,
            autoWatch: isDebug,
            preprocessors: {
                // test bundle entry file
                './test.entry.js': ['webpack']
            },
            webpackMiddleware: {
                stats: 'errors-only'
            }
        },
        (isCoverage ? { coverageReporter } : {}),
        {
            webpack: {
                devtool: 'eval-source-map',
                resolve: {
                    extensions: [
                        '.js',
                        '.ts',
                        '.less',
                        '.html',
                        '.json'
                    ],
                    modules: [path.resolve(process.cwd(), 'src/client'), 'node_modules']
                },
                module: {
                    rules: [
                        {
                            test: /\.js$/,
                            use: [
                                'babel-loader'
                            ],
                            exclude: /node_modules/
                        },
                        {
                            test: /\.ts$/,
                            use: [
                                'babel-loader',
                                {
                                    loader:'ts-loader',
                                    options: {
                                        entryFileIsJs: true
                                    }
                                }
                            ],
                            exclude: /node_modules/
                        }
                    ].concat(isCoverage ? [instrumentLoader] : [])
                }
            }
        }
    ));
};

Then create the test bundle entry point:

// /test/test.entry.js
// Add polyfills/vendor libs for your tests
import 'babel-core/register';
import 'babel-polyfill';

// Build a context of files matching our pattern
const context = require.context('./', true, /^\.\/.*\.spec\.(js|ts)$/);

// Require all of the files in the context
context.keys().forEach(context);

And then you can invoke Karma with a path to the karma.conf.js and parameters for --debug and --coverage:

$ karma start ./test/karma.conf.js --coverage=true --debug=false

Or better yet, put it into package.json scripts property:

// package.json
{
    ...
    "scripts": {
        "test": "cross-env karma ./test/karma.conf.js",
        "test:watch": "cross-env karma ./test/karma.conf.js --debug=true",
        "test:coverage": "cross-env karma ./test/karma.conf.js --coverage=true",
    }
    ...
}

And invoke with npm: npm run test

2 Comments

I did not accept this as answer because it uses AngularJs, which this project has to do without. But I did upvote because you provided a lot of useful context and explanation. Thanks.
@KoertvanKleef I had misunderstood your question, I was under the impression you were trying to run TS tests in an Angular 1.x project without TS. I've removed all the Angular references and updated some of the comments.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.