Nightmare.js and Code Coverage - javascript

Short version:
I am unable to see the code coverage from my tests that I have written using nightmare.js and mocha. I have already tried to use istanbul and _mocha with no luck so far.
Big version:
I have a little project:
/public/index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Website</title>
<script src="./js/hello.js"></script>
</head>
<body>
<h1>My Website</h1>
</body>
</html>
/public/js/hello.js
window.hello = function hello(){
return 'world';
};
The site is running using express and forever.
When I am trying to test it using nightmare.js.
/test/test.js
var path = require('path');
var Nightmare = require('nightmare');
var should = require('should');
/*global describe */
/*global it */
describe('Simple demo', function () {
this.timeout(15000);
var url = 'http://localhost:9000';
it('check hello world result', function (done) {
new Nightmare()
.goto(url)
.evaluate(function () {
/*global hello */
return hello();
}, function (value) {
var expected = "world";
if (value === null) {
false.should.equal(true);
return done();
}
value.should.equal(expected);
return done();
})
.run();
});
it('should get the index title', function (done) {
var expected = 'My Website';
new Nightmare()
.goto(url)
.title(function (title) {
title.should.equal(expected);
done();
})
.run();
});
});
The tests are passing
$ mocha
Simple demo
✓ check hello world result (2089ms)
title = Alexandria
✓ should get the index title (1947ms)
2 passing (4s)
But, I am unable to get code coverage reports from my tests.
I have already tried some commands like:
$ istanbul cover _mocha -- test/test.js -u exports -R spec
No coverage information was collected, exit without writing coverage information
$ istanbul cover --hook-run-in-context _mocha -- -R spec
No coverage information was collected, exit without writing coverage information
So, someone was able to create code coverage reports of nightmare.js tests? If no, there is something close to that using another tools?

I had exactly this same problem in my project. I couldn't find any libraries or configurations which solve this problem in simply way but with some implementation and Grunt configuration you can get code coverage from Grunt process.
Dependencies which I used in my project:
"chai": "^3.5.0",
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-copy": "^0.8.2",
"grunt-express-server": "^0.5.3",
"grunt-istanbul": "^0.7.1",
"grunt-mocha-test": "^0.12.7",
"istanbul": "^0.4.4",
"nightmare": "^2.2.0"
My project structure:
public/ -- public folder for index.html
src/ -- source folder for hello.js
test/ -- mocha tests implementation
server/ -- express implementation for server.js
coverage/ -- HTML report from code coverage
report/ -- HTML report from mocha tests
dist/ -- folder which is used by express server to get content, generated by Grunt
Steps which you have to run from Grunt:
grunt.registerTask('test_hello', [
'clean', // clean dist/ folder
'copy:public', // copy public files from public/ (ex. index.html) to dist/
'instrument', // generate instruments (ex. hello.js) for code coverage from src/ by istanbul
'express', // run express server from dist/ folder
'mochaTest', // run mocha tests with nightmare and generate HTML report to report/ folder
'report_coverage' // generate HTML report from code coverage to coverage/ folder
]);
Grunt configurations:
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-express-server');
grunt.loadNpmTasks('grunt-istanbul');
grunt.loadNpmTasks('grunt-mocha-test');
grunt.initConfig({
clean: {
dist: ['dist/', 'report/', 'coverage/']
},
copy: {
public: {
expand: true,
cwd: 'public/',
src: ['**'],
dest: 'dist/'
}
},
instrument: {
files: ['**/*.js'],
options: {
cwd: 'src/',
lazy: true,
basePath: 'dist/'
}
},
express: {
dev: {
options: {
port: 9000,
script: 'server/server.js',
background: true
}
}
},
mochaTest: {
hello: {
options: {
timeout: 10000,
captureFile: 'report/results.txt', // Optionally capture the reporter output to a file
quiet: false, // Optionally suppress output to standard out (defaults to false)
clearRequireCache: false // Optionally clear the require cache before running tests (defaults to false)
},
src: ['test/*.js']
},
}
});
grunt.registerTask('report_coverage', function () {
var coverage = require('./test/utils/coverage');
coverage.generateReport();
});
grunt.registerTask('test_hello', [
'clean', // clean dist/ folder
'copy:public', // copy public files from public/ (ex. index.html) to dist/
'instrument', // generate instruments (ex. hello.js) for code coverage from src/ by istanbul
'express', // run express server from dist/ folder
'mochaTest:hello', // run mocha tests with nightmare and generate HTML report to report/ folder
'report_coverage' // generate HTML report from code coverage to coverage/ folder
]);
}
I created also class which allows me collect code coverage from each Nightmare instance:
var istanbul = require('istanbul');
var reporter = new istanbul.Reporter(),
sync = true;
var Coverage = function () {
this.loadCodeCoverage = function (dom, done) {
dom
.evaluate(function () {
return window.__coverage__; // this variable store all information about code coverage
})
.then(function (coverageDate) {
if (coverageDate) {
this.getCollector().add(coverageDate);
}
done();
}.bind(this))
.catch(function (error) {
done(error);
});
}
// load page by nightmare
this.getCollector = function () {
if (!this.collector) {
this.collector = new istanbul.Collector();
}
return this.collector;
}
this.generateReport = function () {
reporter.add('text');
reporter.addAll(['lcov', 'clover']);
reporter.write(this.collector, sync, function () {
console.log('All reports generated');
});
}
}
module.exports = new Coverage();
For the above configuration file test/test.js should has the below structure:
var should = require('should');
var coverage = require('./utils/coverage');
/*global describe */
/*global it */
describe('Simple demo', function () {
this.timeout(15000);
var url = 'http://localhost:9000';
before(function (done) {
global.dom = new Nightmare()
.goto(url)
.evaluate(function () {
return 'test';
})
.then(function(result) {
done();
})
.catch(function(error) {
done(error);
});
});
after(function (done) {
coverage.loadCodeCoverage(dom, done);
});
it('check hello world result', function (done) {
dom.evaluate(function () {
/*global hello */
return hello();
})
.then(function(value) {
var expected = "world";
if (value === null) {
false.should.equal(true);
return done();
}
value.should.equal(expected);
return done();
})
.catch(function(error) {
done(error);
});
});
it('should get the index title', function (done) {
var expected = 'My Website';
dom.title(function (title) {
title.should.equal(expected);
done();
});
});
});
If everything works fine you should get on the console information and report from code coverage in HTML form should be generated in folder coverage/
Running "report_coverage" task
------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
------------------------------------|----------|----------|----------|----------|----------------|
js/ | 100 | 100 | 100 | 100 | |
hello.js | 100 | 100 | 100 | 100 | |
------------------------------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
------------------------------------|----------|----------|----------|----------|----------------|
Problem which I still have is that for each test description I have to add method before and after. Could be nice to have these implementations only in one place, example somewhere in Grunt configuration that i needn't remember about them when I'm doing new test description.
It will be very appreciated if someone find more general solution for before and after methods.

I want to propose another approach. It basically boils down to
Instrument the application code with Istanbul
Fetch the coverage data from the browser and pass it to the test process (so we can extract it)
Run the reports
Here's a code snippet that does this
nightmare
.evaluate(() => window.__coverage__) // this executes in browser scope
.end() // terminate the Electron (browser) process
.then((cov) => {
// this executes in Node scope
// handle the data passed back to us from browser scope
const strCoverage = JSON.stringify(cov);
const hash = require('crypto').createHmac('sha256', '')
.update(strCoverage)
.digest('hex');
const fileName = `/tmp/coverage-${hash}.json`;
require('fs').writeFileSync(fileName, strCoverage);
done(); // the callback from the test
})
.catch(err => console.log(err));
for detailed information and links to actual commits please check my blog:
http://atodorov.org/blog/2017/08/12/code-coverage-from-nightmarejs-tests/

Related

Local changes to file in node_modules (Angular 2)

I know that this can be a very stupid question, but I can't find matches with other posts on stackoverflow...
So: Can I modify a file of an external module , just save the file and do something that my app can listen?
At the moment, i'm trying ti change some scss style at the ng2-datepicker module (inside node_modules folder), but if I save and the launch ng serve, changes will not affect my project.
I know it's a simple problem, but i don't know the background architecture of an Angular2 project.
Thanks in advance.
(ps I've seen that i can fork the git and then do something like npm install.
Very interesting, but i also want to know how to have the same result in local)
If you are using gulp file you can tell the changed lib file path to copy to build folder check gulp.task('copy-libs') in code below git repo for angular2-tour-of-heroes using gulp
const gulp = require('gulp');
const del = require('del');
const typescript = require('gulp-typescript');
const tscConfig = require('./tsconfig.json');
const sourcemaps = require('gulp-sourcemaps');
const tslint = require('gulp-tslint');
const browserSync = require('browser-sync');
const reload = browserSync.reload;
const tsconfig = require('tsconfig-glob');
// clean the contents of the distribution directory
gulp.task('clean', function () {
return del('dist/**/*');
});
// copy static assets - i.e. non TypeScript compiled source
gulp.task('copy:assets', ['clean'], function() {
return gulp.src(['app/**/*', 'index.html', 'styles.css', '!app/**/*.ts'], { base : './' })
.pipe(gulp.dest('dist'))
});
// copy dependencies
gulp.task('copy:libs', ['clean'], function() {
return gulp.src([
'node_modules/angular2/bundles/angular2-polyfills.js',
'node_modules/systemjs/dist/system.src.js',
'node_modules/rxjs/bundles/Rx.js',
'node_modules/angular2/bundles/angular2.dev.js',
'node_modules/angular2/bundles/router.dev.js',
'node_modules/node-uuid/uuid.js',
'node_modules/immutable/dist/immutable.js'
'yourpath/changedFileName.js'
])
.pipe(gulp.dest('dist/lib'))
});
// linting
gulp.task('tslint', function() {
return gulp.src('app/**/*.ts')
.pipe(tslint())
.pipe(tslint.report('verbose'));
});
// TypeScript compile
gulp.task('compile', ['clean'], function () {
return gulp
.src(tscConfig.files)
.pipe(sourcemaps.init())
.pipe(typescript(tscConfig.compilerOptions))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('dist/app'));
});
// update the tsconfig files based on the glob pattern
gulp.task('tsconfig-glob', function () {
return tsconfig({
configPath: '.',
indent: 2
});
});
// Run browsersync for development
gulp.task('serve', ['build'], function() {
browserSync({
server: {
baseDir: 'dist'
}
});
gulp.watch(['app/**/*', 'index.html', 'styles.css'], ['buildAndReload']);
});
gulp.task('build', ['tslint', 'compile', 'copy:libs', 'copy:assets']);
gulp.task('buildAndReload', ['build'], reload);
gulp.task('default', ['build']);

Browser Sync multiple reloads

I'm new to Gulp.js and I'm trying to create a template for static websites using slim/coffee/sass and Browser Sync.
I have an issue with my slim task (compile slim to html): when I edit a slim file gulp run the slim task a lot of times depending on the number of slim files in the project. (so if I have 5 slim files and I edit the title of one of them, gulp will run the slim task 5 times.)
The problem is that in this task I ask Browser Sync to reload the server whenever I edit a slim file. Using again the exemple above, if I edit a title in one file, the server will reload 5 times. And this is realy annoying.
I hope someone got a solution for this issue :)
Here the slim task in my gulpfile:
gulp.task('slim', function () {
return gulp.src(slim_dev + '/**/*.slim')
// prevent server from crashing
.pipe(plugins.plumber({ errorHandler: function(err) {
plugins.notify.onError({
title: "Gulp error in " + err.plugin
})(err);
}}))
// compile slim to html
.pipe(plugins.slim({
pretty: false,
include: true
}))
// minify html
.pipe(plugins.minifyHtml())
// copy result to build folder
.pipe(gulp.dest(slim_build))
// reload server on slim save
.pipe(reload({stream:true}))
// notify when task completed
.pipe(plugins.notify('Slim compilation completed !'));
});
Here the global gulpfile:
// GULP TEMPLATE - Gulfile.js - Victor Allegret
//
// - $ gulp
// - $ gulp build
// - $ gulp clean
//
// --------------------------------------------------------
////////////////////
// VARIABLES
////////////////////////////////////////////////////////////////////////////////
// REQUIRE
// ---------------------------------------------------------
var gulp = require('gulp'),
plugins = require('gulp-load-plugins')({
pattern: '*'
}),
reload = plugins.browserSync.reload
// PATH
// ---------------------------------------------------------
///// PATHS FOR DEV
var slim_dev = './dev/views/',
sass_dev = './dev/assets/stylesheets/',
coffee_dev = './dev/assets/javascripts/',
fonts_dev = './dev/assets/fonts/',
img_dev = './dev/assets/images/',
dev = './dev';
///// PATH FOR PROD
var slim_build = './build/views/',
sass_build = './build/assets/stylesheets/',
coffee_build = './build/assets/javascripts/',
fonts_build = './build/assets/fonts/',
img_build = './build/assets/images/',
build = './build';
////////////////////
// TASKS
////////////////////////////////////////////////////////////////////////////////
// COMPILE SLIM TO HTML
// ---------------------------------------------------------
gulp.task('slim', function () {
return gulp.src(slim_dev + '/**/*.slim')
// prevent server from crashing
.pipe(plugins.plumber({ errorHandler: function(err) {
plugins.notify.onError({
title: "Gulp error in " + err.plugin
})(err);
}}))
// compile slim to html
.pipe(plugins.slim({
pretty: false,
include: true
}))
// minify html
.pipe(plugins.minifyHtml())
// copy result to build folder
.pipe(gulp.dest(slim_build))
// reload server on slim save
.pipe(reload({stream:true}))
// notify when task completed
.pipe(plugins.notify('Slim compilation completed !'));
});
// COMPILE SASS TO CSS
// ---------------------------------------------------------
gulp.task('sass', function () {
return gulp.src(sass_dev + '/**/*.{sass,css,scss}')
// prevent server from crashing
.pipe(plugins.plumber({ errorHandler: function(err) {
plugins.notify.onError({
title: "Gulp error in " + err.plugin,
})(err);
}}))
// compile sass to css
.pipe(plugins.sass())
// add auto-prefixes
.pipe(plugins.autoprefixer({
browsers: ['last 2 versions'],
cascade: false
}))
// concat all files
.pipe(plugins.concat('main.css'))
// rename to .min
.pipe(plugins.rename('main.min.css'))
// minify css
.pipe(plugins.minifyCss())
// copy result to build folder
.pipe(gulp.dest(sass_build))
// reload on sass save
.pipe(reload({stream:true}))
// notify when task completed
.pipe(plugins.notify('Sass compilation completed !'));
});
// COMPILE COFFEE TO JS
// ---------------------------------------------------------
gulp.task('coffee', function() {
return gulp.src(coffee_dev + '/**/*.coffee')
// compile coffee to js
.pipe(plugins.coffee())
// concat all files
.pipe(plugins.concat('all.js'))
// rename to .min
.pipe(plugins.rename('all.min.js'))
// minify js
.pipe(plugins.uglify())
// copy result to build folder
.pipe(gulp.dest(coffee_build))
// notify when task completed
.pipe(plugins.notify('Coffee compilation completed !'));
});
// FONTS
// ---------------------------------------------------------
gulp.task('fonts', function() {
return gulp.src(fonts_dev + '/**/*.{eot,svg,ttf,woff}')
// copy result to build folder
.pipe(gulp.dest(fonts_build))
});
// REMOVE UNUSED CSS
// ---------------------------------------------------------
gulp.task('uncss', function () {
return gulp.src(sass_build + '/app.css')
// remove unused css
.pipe(plugins.uncss({
html: [build + '/**/*.html']
}))
// minify css
.pipe(plugins.minifyCss())
// copy result to build folder
.pipe(gulp.dest(sass_build))
// notify when task completed
.pipe(plugins.notify('Unused CSS removed !'));
});
// MINIFY IMAGES
// ---------------------------------------------------------
gulp.task('img', function () {
return gulp.src(img_dev + '/**/*.{png,jpg,jpeg,gif,svg}')
// minify images
.pipe(plugins.imagemin())
// copy result to build folder
.pipe(gulp.dest(img_build))
// notify when task completed
.pipe(plugins.notify('Images are optimized!'));
});
// REMOVE BUILD FOLDER
// ---------------------------------------------------------
gulp.task('removeBuild', function () {
return gulp.src(build, { read: false})
.pipe(plugins.rimraf())
.pipe(plugins.notify('Prod folder deleted !'));
});
////////////////////
// COMMANDS
////////////////////////////////////////////////////////////////////////////////
// RUN SLIM | SASS | COFFEE ($ gulp dev)
// ---------------------------------------------------------
gulp.task('dev', ['slim', 'sass', 'coffee', 'fonts']);
// RUN SLIM | SASS | COFFEE | UNCSS | IMG ($ gulp build)
// ---------------------------------------------------------
gulp.task('build', ['slim', 'sass', 'coffee', 'fonts', 'uncss', 'img']);
// RUN CLEAN ($ gulp clean)
// ---------------------------------------------------------
gulp.task('clean', ['removeBuild']);
// RUN SERVER ($ gulp)
// ---------------------------------------------------------
///// WATCH
gulp.task('watch', ['dev'], function () {
plugins.browserSync.init({
server: {
baseDir: slim_build,
index: "index.html"
},
scrollProportionally: true,
notify: false
})
gulp.watch(dev + '/**/*.slim', ['slim']);
gulp.watch(dev + '/**/*.sass', ['sass']);
gulp.watch(dev + '/**/*.coffee', ['coffee']);
gulp.watch(build + '/**/*.html').on('change', reload);
});
////// COMMAND
gulp.task('default', ['watch'])
Thanks a lot !
See browserSync stream option : once. So change this:
.pipe(reload({stream:true})) // in 'slim' task
to
stream = plugins.browserSync.stream // for the "slim' task
.pipe(stream({once:true})) // in 'slim' task
That option is supposed to reload browserSync only once per stream.

Including Jotted's Codemirror plugin with angular gulp

I am trying to use the Jotted javascript library in conjunction with a gulp-angular project. The library itself works fine, but I'm trying to include the library's Codemirror plugin and am running into issues. When I try to add the library and run gulp serve, I get this error:
Error in parsing: "js/codemirror.js", Line 4: Unexpected reserved word
The line causing the issue looks like so:
import * as util from '../util.js'
I've seen a few people reference using babel to fix this sort of issue, but the solutions seem a tad beyond my skill level.
Here's a link to the plugin:
https://github.com/ghinda/jotted
My current server.js
'use strict';
var path = require('path');
var gulp = require('gulp');
var conf = require('./conf');
var babel = require('gulp-babel');
var browserSync = require('browser-sync');
var browserSyncSpa = require('browser-sync-spa');
var util = require('util');
var proxyMiddleware = require('http-proxy-middleware');
function browserSyncInit(baseDir, browser) {
browser = browser === undefined ? 'default' : browser;
var routes = null;
if (baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
routes = {
'/bower_components': 'bower_components'
};
}
var server = {
baseDir: baseDir,
routes : routes
};
/*
* You can add a proxy to your backend by uncommenting the line below.
* You just have to configure a context which will we redirected and the target url.
* Example: $http.get('/users') requests will be automatically proxified.
*
* For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.9.0/README.md
*/
// server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true});
browserSync.instance = browserSync.init({
startPath: '/',
server : server,
browser: browser,
host: '192.168.0.20',
https: false,
port : parseInt(process.env.GULP_PORT) || 8684
});
}
browserSync.use(browserSyncSpa({
selector: '[ng-app]'// Only needed for angular apps
}));
gulp.task('serve', ['watch'], function () {
browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
});
gulp.task('serve:dist', ['build'], function () {
browserSyncInit(conf.paths.dist);
});
gulp.task('serve:e2e', ['inject'], function () {
browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
});
gulp.task('serve:e2e-dist', ['build'], function () {
browserSyncInit(conf.paths.dist, []);
});
var gulp = require('gulp');
var webserver = require('gulp-webserver');
gulp.task('webserver', function() {
gulp.src('src')
.pipe(webserver({
host: '0.0.0.0',
livereload: true,
directoryListing: true,
open: true
}));
});
My current gulpfile.babel.js
/**
* Welcome to your gulpfile!
* The gulp tasks are splitted in several files in the gulp directory
* because putting all here was really too long
*/
'use strict';
var gulp = require('gulp');
var wrench = require('wrench');
var babel = require('gulp-babel');
/**
* This will load all js or coffee files in the gulp directory
* in order to load all gulp tasks
*/
wrench.readdirSyncRecursive('./gulp').filter(function (file) {
return (/\.(js|coffee)$/i).test(file);
}).map(function (file) {
require('./gulp/' + file);
});
/**
* Default task clean temporaries directories and launch the
* main optimization build task
*/
gulp.task('default', ['clean'], function () {
gulp.start('build');
});
Stack trace
...standard build procedure...
[15:12:01] all files 224.05 kB
[15:12:01] Finished 'scripts' after 2.64 s
[15:12:01] Starting 'inject'...
[15:12:01] gulp-inject 10 files into index.html.
[15:12:02] [AngularFilesort] Error in plugin 'gulp-angular-filesort'
Message:
Error in parsing: "js/codemirror.js", Line 4: Unexpected reserved word
[15:12:02] gulp-inject Nothing to inject into index.html.
[15:12:02] Finished 'inject' after 337 ms
[15:12:02] Starting 'watch'...
[15:12:02] Finished 'watch' after 93 ms
[15:12:02] Starting 'serve'...
[15:12:02] Finished 'serve' after 24 ms
...standard serve procedure...
My guess is that you run the code with the import statement on a ES5 environment, which does not support modules.
If you run it in Node like I suspect, you can use required instead.

Why is Cucumber not executing my step definitions?

Working on Windows I have installed Ruby and the Ruby DevKit to get things working with Cucumber. Now I have the following basic setup:
/app
/features
example.feature
/step_definitions
example.steps.js
In the example.feature file I have:
Feature: This is an example feature
In order to learn Cucumber
As a developer
I want to make this feature pass
Scenario: wrote my first scenario
Given a variable set to 1
When I increment the variable by 2
Then the variable should contain 3
And in the example.step.js file I have:
'use strict';
module.exports = function () {
this.givenNumber = 0;
this.Given(/^a variable set to (\d+)$/, function(number, next) {
this.givenNumber = parseInt(number);
next();
});
this.When(/^I increment the variable by (\d+)$/, function (number, next) {
this.givenNumber = this.givenNumber + parseInt(number);
next();
});
this.Then(/^the variable should contain (\d+)$/, function (number, next) {
if (this.givenNumber != number)
throw(new Error("This test didn't pass, givenNumber is " + this.givenNumber + " expected 0"));
next();
});
};
Now, when I run 'cucumber' from the /app dir I keep getting the folowing output:
1 scenario (1 undefined)
3 steps (3 undefined)
0m0.004s
I tried moving around the files, adding the --require option, etc. but nothing seems to be helping.
Any ideas?
Apparently this cannot be executed directly using the 'cucumber' command.
Settings things up using grunt and the grunt-cucumber task seems to be working as expected:
My Gruntfile.js
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
cucumberjs: {
src: 'features',
options: {
steps: 'features/step_definitions',
format: 'pretty'
}
}
});
grunt.loadNpmTasks('grunt-cucumber');
grunt.registerTask('default', ['cucumberjs']);
};
Additionally: if you are using protractor. It has cucumber build in. Just create the right config for protractor (protractor.conf.js):
exports.config = {
specs: [
//'e2e/features/*.feature'
'**/*.feature'
],
capabilities: {
'browserName': 'chrome'
},
baseUrl: 'http://localhost:9000/',
framework: 'cucumber'
}

Gulp: target to debug mocha tests

I have a set of gulp.js targets for running my mocha tests that work like a charm running through gulp-mocha. Question: how do I debug my mocha tests running through gulp? I would like to use something like node-inspector to set break points in my src and test files to see what's going on. I am already able to accomplish this by calling node directly:
node --debug-brk node_modules/gulp/bin/gulp.js test
But I'd prefer a gulp target that wraps this for me, e.g.:
gulp.task('test-debug', 'Run unit tests in debug mode', function (cb) {
// todo?
});
Ideas? I want to avoid a bash script or some other separate file since I'm trying to create a reusable gulpfile with targets that are usable by someone who doesn't know gulp.
Here is my current gulpfile.js
// gulpfile.js
var gulp = require('gulp'),
mocha = require('gulp-mocha'),
gutil = require('gulp-util'),
help = require('gulp-help');
help(gulp); // add help messages to targets
var exitCode = 0;
// kill process on failure
process.on('exit', function () {
process.nextTick(function () {
var msg = "gulp '" + gulp.seq + "' failed";
console.log(gutil.colors.red(msg));
process.exit(exitCode);
});
});
function testErrorHandler(err) {
gutil.beep();
gutil.log(err.message);
exitCode = 1;
}
gulp.task('test', 'Run unit tests and exit on failure', function () {
return gulp.src('./lib/*/test/**/*.js')
.pipe(mocha({
reporter: 'dot'
}))
.on('error', function (err) {
testErrorHandler(err);
process.emit('exit');
});
});
gulp.task('test-watch', 'Run unit tests', function (cb) {
return gulp.src('./lib/*/test/**/*.js')
.pipe(mocha({
reporter: 'min',
G: true
}))
.on('error', testErrorHandler);
});
gulp.task('watch', 'Watch files and run tests on change', function () {
gulp.watch('./lib/**/*.js', ['test-watch']);
});
With some guidance from #BrianGlaz I came up with the following task. Ends up being rather simple. Plus it pipes all output to the parent's stdout so I don't have to handle stdout.on manually:
// Run all unit tests in debug mode
gulp.task('test-debug', function () {
var spawn = require('child_process').spawn;
spawn('node', [
'--debug-brk',
path.join(__dirname, 'node_modules/gulp/bin/gulp.js'),
'test'
], { stdio: 'inherit' });
});
You can use Node's Child Process class to run command line commands from within a node app. In your case I would recommend childprocess.spawn(). It acts as an event emitter so you can subscribe to data to retrieve output from stdout. In terms of using this from within gulp, some work would probably need to be done to return a stream that could be piped to another gulp task.

Categories

Resources