uncategorized

Unit Testing Gulp Tasks with Jasmine

I have been doing a bit of work in gulp lately and needed a way to test individual tasks in Jasmine. I found a lot of
information on how to run a jasmine test from gulp, but I needed the reverse. I need to run a gulp task from jasmine.

The pattern I use for gulp task is to define the tasks in individual files. For example here is babelFy.js

1
'use strict';
module.exports = (gulp, plugins, paths)=>   () => {
	if (paths.babeloptions && paths.babeloptions.sourceMaps) {
		if (paths["debug"]) {
			return gulp.src(paths.src.babelFy, {base: paths.base})
				.pipe(plugins.sourcemaps.init())
				.pipe(plugins.babel(paths.babeloptions))
				.pipe(plugins.sourcemaps.write('.'))
				.pipe(plugins.debug())
				.pipe(gulp.dest(paths.dest));
		} else {
			return gulp.src(paths.src.babelFy, {base: paths.base})
				.pipe(plugins.sourcemaps.init())
				.pipe(plugins.babel(paths.babeloptions))
				.pipe(plugins.sourcemaps.write('.'))
				.pipe(gulp.dest(paths.dest));
		}
	} else {
		if (paths["debug"]) {
			return gulp.src(paths.src.babelFy, {base: paths.base})
				.pipe(plugins.babel(paths.babeloptions))
				.pipe(plugins.debug())
				.pipe(gulp.dest(paths.dest));
		} else {
			return gulp.src(paths.src.babelFy, {base: paths.base})
				.pipe(plugins.babel(paths.babeloptions))
				.pipe(gulp.dest(paths.dest));
		}
	}
}

In my gulpfile I have:

1
var u = require("./utilities/index");
module.exports =(gulp,ns,configFile)=> {
	if(ns && !u.endsWith(ns,".")){
		ns=ns+".";
	}
	ns=ns||"";
	var plugins = require('gulp-load-plugins')();
	var runSequence = require('run-sequence').use(gulp);
	var paths = require('./setPathsGulp')(configFile);
	var ns="server";
gulp.task(ns+'babelFy', require('./gulp/defaultTasks/babelFy')(gulp, plugins, paths));

All the namespacing and configFile details are unimportant. They arise because I am using this
as a submodule of the main gulpfile but that’s a topic for another day.
The important point is that I am actually creating a gulp task by invoking the function exposed in babelFy.js
The function has some logic in it to branch based on whether I want to include sourcemaps and whether I am in
debug mode. In all cases, however, it returns a function that gulp invokes when you run a task.

This is just javascript so if I invoke the same function in a jasmine test I can accomplish the same thing that gulp does.

Here is an example of a jasmine test.

1
'use strict';
let Promise = require("bluebird"),
	fs = Promise.promisifyAll(require("fs")),
	path = require("path")
let gulp = require("gulp");
let plugins = require('gulp-load-plugins')();
let sourceFile=require(__dirname+"/../gulp/serverConfigv4.json");
let paths = require("../index")(gulp, "bw", sourceFile);
let clean = (destination)=>new Promise((resolve, reject)=> {
	let del = require('del');
	if (typeof destination === 'string') {
	del([
		destination
	], {force: true})
		.then(resolve)
		.catch(reject)
}else{
		//an array
		del(
			destination
		, {force: true})
			.then(resolve)
			.catch(reject)
	}
})
describe("testBabelFyServer", ()=> {
	beforeEach((done)=> {
		clean(path.resolve(paths.dest) + "/**/*")
			.then((results)=> {
				done();
			})
			.catch((err)=> {
				expect(err).toEqual(null);
				done();
			});
	});
	it("babelfyhasfatarrows", (done)=> {
		let task = require("../gulp/defaultTasks/babelFy");
		let stream = task(gulp, plugins, paths)();
		stream.on("end", ()=> {
			let dir = path.resolve(paths.dest);
			fs.readdirAsync(dir )
				.then((results)=> {
					expect(results.indexOf("index.js")).toBeGreaterThan(0);
					expect(results.indexOf("index.js.map")).toBeGreaterThan(0);
					fs.readFileAsync(dir+"/index.js","utf8")
					.then((content)=>{
							expect(content.indexOf("=>")).toBeGreaterThan(0);
							done();
						})
						.catch((err)=> {
							console.log(err,err.stack);
							expect(err).toEqual(null);
							done();
						})

				})
				.catch((err)=> {
					expect(err).toEqual(null);
					done();
				});
		});
	});

})

The code above “describe” initializes some variables my babelFy function needs and defines a utility function that
deletes files so I know I am working with fresh files. Notice that this is very similar to the initialization I do
in the gulpfile. In the gulp file I am just defining the function in gulp.task(…) where it is just called “task” in
the jasmine test.

The beforeEach function does some cleanup. In particular, the “dest” directory is wiped because the actual test
is going to write to that location.

Now lets look at the actual test:

1
it("babelfyhasfatarrows", (done)=> {

The “done” parameter lets jasmine know this is an asynchronous test.

1
let task = require("../gulp/defaultTasks/babelFy");

This fetchs the task. Remember that the task returns a function.

1
let stream = task(gulp, plugins, paths)();

Now we invoke that function. That is what gulp does when you run a task. Gulp return a stream when you run a task.
We listen to the “end” event for the stream so that we can analyze the results when the task is finished.

The code in the “end” handler is where the actual validation occurs. We make sure the appropriate files were copied and we test to see if arrow functions
still exist in the transpiled file. For this test we are using a babel configuration that should not transform the
arrow functions because the target code is to be run in node Argon (4.2.1) which handles them natively.

We have other tests where we set the babel options to transform them, in which case we expect to not have “=>” present
in the files. Those babel options are set in our sourceFile.

All that remains is to execute “done” when our asynchronous process is over. Of course we need to do that in
our promise catchs as well.

##Summary
We have successfully created jasmine tasks that test the validity of a gulp task. In order to do this we must:

  1. Define our gulp task, not by an anonymous function but one contained in it’s own file so it can be fetched external to gulp.
  2. Initialize our test the same way we initialize in our gulpfile.
  3. Realize that a gulp task returns a stream.
  4. Perform our validation after the “end” event is emitted by the stream.
  5. Fire “done()” on either errors or when we have finished validation.

This is all part of the work I have been doing on a wrapper which allows you to transpile different parts of your code
with different options. For example you may want limited transpiling on the server but much more on the client code. Details
on that project can be found here: gulp-babel-wrap

Share