📜 ⬆️ ⬇️

JS testing. Karmic webpack

image

Hello!

A couple of months ago I wrote a post about how to teach a webpack for spa .
Since then, the tool has stepped forward and has acquired an additional number of plug-ins, as well as examples of configurations.
')
In this article I want to share the experience of mixing the explosive mix of webpack + jasmine + chai + karma.

In my opinion, the best book about automated testing by Christian Johansen - Test-Driven JavaScript Development - identifies the problems that the developer faces when writing code without tests:

- The code is written, but the behavior is not available in the browser (example .bind () and IE 8);
- Implementation is changed, but a set of components leads to erroneous or non-working functionality;
- New code is written, you need to take care of the behavior with the old interfaces.

Based on experience, I say.
Programmers who choose the TDD (Test-driven Development) Samurai path spend a lot of time covering the code with tests. As a result, remain in the win during the testing phase and catching bugs.

Glossary


- Webpack - modular asset collector;
- Karma - test-runner for JavaScript;
- Jasmine - a tool for determining the tests in the style of BDD;
- Chai - a library for testing conditions, expect , assert , should;

Package installation


To begin with, I will provide a list of packages that are additionally installed in the project. For this we use npm .

#tools npm i chai mocha phantomjs-prebuilt --save-dev #karma packages #1 npm i karma karma-chai karma-coverage karma-jasmine --save-dev #karma packages #2 npm i karma-mocha karma-mocha-reporter karma-phantomjs-launcher --save-dev #karma packages #3 npm i karma-sourcemap-loader karma-webpack --save-dev 

Go ahead.

Setting up the environment


After installing additional packages, configure the karma configuration. To do this, in the root of the project, create a file karma.conf.js

 touch karma.conf.js 

With the following content:

 // karma.conf.js var webpackConfig = require('testing.webpack.js'); module.exports=function(config) { config.set({ //       coverageReporter: { dir:'tmp/coverage/', reporters: [ { type:'html', subdir: 'report-html' }, { type:'lcov', subdir: 'report-lcov' } ], instrumenterOptions: { istanbul: { noCompact:true } } }, // spec ,     **_*.spec.js_** files: [ 'app/**/*.spec.js' ], frameworks: [ 'chai', 'jasmine' ], //       reporters: ['mocha', 'coverage'], preprocessors: { 'app/**/*.spec.js': ['webpack', 'sourcemap'] }, plugins: [ 'karma-jasmine', 'karma-mocha', 'karma-chai', 'karma-coverage', 'karma-webpack', 'karma-phantomjs-launcher', 'karma-mocha-reporter', 'karma-sourcemap-loader' ], //   webpack webpack: webpackConfig, webpackMiddleware: { noInfo:true } }); }; 

Configuring a webpack:

 // testing.webpack.js 'use strict'; // Depends var path = require('path'); var webpack = require('webpack'); module.exports = function(_path) { var rootAssetPath = './app/assets'; return {  cache: true,  devtool: 'inline-source-map',  resolve: {   extensions: ['', '.js', '.jsx'],   modulesDirectories: ['node_modules']  },  module: {   preLoaders: [    {     test: /.spec\.js$/,     include: /app/,     exclude: /(bower_components|node_modules)/,     loader: 'babel-loader',     query: {      presets: ['es2015'],      cacheDirectory: true,     }    },    {     test: /\.js?$/,     include: /app/,     exclude: /(node_modules|__tests__)/,     loader: 'babel-istanbul',     query: {      cacheDirectory: true,     },    },   ],   loaders: [    // es6 loader    {     include: path.join(_path, 'app'),     loader: 'babel-loader',     exclude: /(node_modules|__tests__)/,     query: {      presets: ['es2015'],      cacheDirectory: true,     }    },    // jade templates    { test: /\.jade$/, loader: 'jade-loader' },    // stylus loader    { test: /\.styl$/, loader: 'style!css!stylus' },    // external files loader    {     test: /\.(png|ico|jpg|jpeg|gif|svg|ttf|eot|woff|woff2)$/i,     loader: 'file',     query: {      context: rootAssetPath,      name: '[path][hash].[name].[ext]'     }    }   ],  }, }; }; 

We are ready to write and run the first test.

Defining spec files


image
Experience shows that specs (from the English spec - specification) are more convenient to store in the same folders as the tested components. Although, of course, you build your own application architecture. In the example below, you will find an example for a trial article, which is located in the tests directory of the boilerplate module.

This naming of directories gives a positive response from new developers who want to become familiar with the functionality of the module or component.

TL; DR opening the project, we see a folder with specifications, located in the first place due to string sorting.

Launch


There is nothing new here.
To start, I use the built-in functionality of the npm scripts section.
Exactly the same as for the dev-server and the "combat" build functionality.

In package.json, we declare the following commands:

 "scripts": { ...  "test:single": "rm -rf tmp/ && karma start karma.conf.js --single-run --browsers PhantomJS",  "test:watch": "karma start karma.conf.js --browsers PhantomJS" ... } 

To run the tests in the "update on change" mode, at the root of the project, type the command:

 npm run test:watch 

For a single run:

 npm run test:single 

image

First test


For example, I propose to consider a non-trivial from the point of view of unit testing task. Processing the result of Backbone.View.
It's okay if the first test looks like a formality.

Consider the View code:

 // view.js module.exports = Backbone.View.extend({ className: 'example', tagName: 'header', template: require('./templates/hello.jade'), initialize: function($el) {  this.$el = $el;  this.render(); }, render: function() {  this.$el.prepend(this.template()); } }); 

It is expected that when creating an instance of View, the render () function will be called. The result of which will be html - declared in the hello.jade template

An example of a formal test covering the functional:

 // boilerplate.spec.js 'use strict'; const $ = require('jquery'); const Module = require('_modules/boilerplate'); describe('App.modules.boilerplate', function() { //     let $el = $('<div>', { class: 'test-div' }); let Instance = new Module($el); //       it('Should be an function', function() {  expect(Module).to.be.an('function'); }); //   new    -   it('Instance should be an object', function() {  expect(Instance).to.be.an('object'); }); //    el  $el  it('Instance should contains few el and $el properties', function() {  expect(Instance).to.have.property('el');  expect(Instance).to.have.property('$el'); }); //       render() it('Instance should contains render() function', function() {  expect(Instance).to.have.property('render').an('function'); }); // $el   dom element it('parent $el should contain rendered module', function() {  expect($el.find('#fullpage')).to.be.an('object'); }); }); 

We start testing and watch the result.
image

In addition, the tmp / coverage / html-report / directory will contain a code coverage report:
image

Conclusion


Tests, even in such a formal form, will relieve us of obligations to ourselves.
By applying sufficient ingenuity in their declaration, we can save ourselves and colleagues from headaches.

In conclusion, imagine the amount of time we spend every day on each iteration: "changed - saved - updated browser - saw the result".
The obvious is near. Testing is a useful tool to guard your time.

Example


See this link webpack-boilerplate

Thanks for reading.

Source: https://habr.com/ru/post/278503/


All Articles