Writing tests to support your Gulp build tooling.

I recently ported a set of rake tasks to gulp. One of the goals was to provide stability to the new tooling through tests.

Overview

I took a slightly different approach for adding tasks to the gulpfile.js by separating out the tasks into individual files. This means tests can be written for specific tasks but also that new tasks can be run immediately without modifying the gulpfile.

The project has a similar directory structure to the following:

config.js
gulp/
  ├── lib/
  |   ├── set-js-version.js
  ├── tasks/
  |   ├── set-js-version.js
  ├── tests/
  |   ├── set-js-version.spec.js
gulpfile.js

The gulpfile.js makes use of the require-dir module for requiring all the tasks in the tasks directory.

(function() {
    'use strict';

    var requireDir = require('require-dir');

    // require all tasks in gulp/tasks, including subfolders
    requireDir('./gulp/tasks', { recurse: true });
})();

Installation

Before beginning, it is important to get all the right libraries installed. Firstly, you'll need to install Gulp globally:

$ npm install -g gulp

Then you'll need to create a package.json file for the project dependencies:

$ npm init

Finally, you'll need to install the dependencies:

$ npm install --save-dev gulp gulp-mocha chai require-dir

Test runner

We are going to need a test runner - this will also be a Gulp task! Copy the code below into a new file tasks/run-tests.js.

(function() {
    'use strict';

    var gulp = require('gulp')
        mocha = require('gulp-mocha');

    gulp.task('test', function() {
        return gulp.src('./tests/*.spec.js', { read: false })
            .pipe(mocha());
    });
})();

A simple task

The gulp/tasks directory will contain a task named setjsversion which we'll use for setting a version number from a config file. A separate task will have already created the build number and updated the config file - this task isn't covered here.

Our objective is to read the config file and update a javascript file which sets a constant.

tests/set-js-version.spec.js

Following a strict TDD approach, the test comes first!

(function() {
    'use strict';

    var expect = require('chai').expect;
    var setJsVersion = require('../lib/set-js-version');
    var fs = require('fs');

    describe('When setting the js version', function() {
        var config, modifiedContents;
        beforeEach(function() {
            var fileContents = 'angular.module(\'app.modules.global\')\n' +
                '.constant(\'appGlobalVersion\', {\n' +
                '    APP_VERSION: \'0.5.5.51622\'\n' +
                '});\n' +
                '\';\n';

            fs.writeFileSync('/tmp/version.js', fileContents);

            config =  {
                paths: {
                    root_path: '/tmp',
                    web_version_path: '/version.js'
                },
                buildInfo: {
                    version: '0.26.1.51025'
                }
            };

            setJsVersion.run(config);

            modifiedContents = fs.readFileSync('/tmp/version.js', {encoding: 'utf8'});
        });

        afterEach(function() {
            fs.unlinkSync('/tmp/version.js');
        });

        it('should set the correct version', function() {
            expect(modifiedContents).to.contain('APP_VERSION: \'0.26.1.51025\'');
        });
    });
})();

The test begins by requiring the Chai expect assertion library, the task helper lib/set-js-version.js and the fs node module for manipulating the file system.

In the set-up (beforeEach), a javascript file is created (so the version number can be updated) along with a sample config object.

Run the test

Tell Gulp to run the test task:

$ gulp test

You should see the following output from Gulp:

[20:52:54] Using gulpfile ~/source/github/testable-gulp/gulpfile.js
[20:52:54] Starting 'test'...

   0 passing (0ms)

[20:52:54] Finished 'test' after 1 ms

This is good, the test didn't pass because we haven't written the implementation yet! Let go ahead and do that now.

lib/set-js-version.js

The following is the implementation for the task.

(function() {
    'use strict';

    var fs = require('fs');

    module.exports.run = function(config) {
        var path = config.paths.root_path + config.paths.web_version_path;
        var fileContents = fs.readFileSync(path, {encoding: 'utf8'});
        fileContents = fileContents.replace(/APP_VERSION:\s?'[^']+'/, 'APP_VERSION: \'' + config.buildInfo.version + '\'');
        fs.writeFileSync(path, fileContents);
    };
})();

Run the test again

When you run the test for the second time:

$ gulp test

You should see the following output verifying that the test has passed!

[20:52:54] Using gulpfile ~/source/github/testable-gulp/gulpfile.js
[20:52:54] Starting 'test'...

    When setting the js version
        ✓ should set the correct version

    1 passing (5ms)

[20:52:54] Finished 'test' after 32 ms

Add the Gulp task

The final piece of the puzzle is the task file tasks/set-js-version.js.

(function () {
    'use strict';

    var gulp = require('gulp');
    var setJsVersion = require('../lib/set-js-version');
    var config = require('../../config');

    gulp.task('setjsversion', function() {
        return setJsVersion.run(config);
    });
})();

You can run the task like this:

$ gulp setjsversion

Summary

Writing a suite of tests around your build tooling offers that extra blanket of stability. Separating your tasks into separate files means that individual tasks can be tested in isolation and this makes maintenance much easier.

Download the source

A repo is available with the source code from this example. You can download the code at:

https://github.com/ducksoupdev/testable-gulp

Take a look at

Building a static site generator in Gulp - a tool for creating a personal, blog or documentation site.