Testing some basic file operations

This post is about testing some basic file operations - using test driven approach - with the help of Nodeunit plugin for Grunt. If you haven't used Grunt or Nodeunit before, I suggest checking out this blog post about basic nodeunit tests.

This small project is based on James Shore: Let's Code: Test Driven Javascript lessons (Chapter 4, Serving Files), but it concentrates on off-line file operations rather than file serving in a client/server process.

We would like to produce:

  • a function, that creates a file (createFile),
  • a function, that reads the file (readFile) and
  • a function, that removes the file (cleanUpfile).

First let's see the tests we need:

  • test whether there is a createFile() function and if it gets a fileName parameter,
  • test if the createFile() function gets a fileContent parameter,
  • test if after calling createFile() the file has been created,
  • test via readFile() if the file contains we gave to createFile()
  • finally, test if the cleanUpFile() removes the file.

Create and clean up temporary directory

Before the tests, it is advisable to generate a temporary directory, where we can test file operations, and after the tests we should remove these directories. I decided to do it in the Gruntfile.js:

module.exports = function(grunt) {

  var GENERATED_DIR= "generated";
  var TESTFILE_DIR = "/test";
 // Project configuration.
  grunt.initConfig({
    jshint: {
      files: ['gruntfile.js', 'src/**/*.js']
    },
    nodeunit: {
      files: ['src/**/*-test.js'],
      options: {
        reporter : 'default'
      }
    }
  });

  // Load the plugin that provides the "jshint task.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-nodeunit');

  //make generate/test directory
  grunt.registerTask('makeGeneratedDir',function(){  
    grunt.file.mkdir(GENERATED_DIR + TESTFILE_DIR);
  });

  //delete generated directory
  grunt.registerTask('cleaning', function(){
    grunt.file.delete(GENERATED_DIR);
  });

  // Default task(s).  
  grunt.registerTask('default', ['jshint', 'makeGeneratedDir','nodeunit','cleaning']);
};

As you can see the Gruntfile.js has four tasks. First, jshint checks the codes for errors, then makeGeneratedDir creates our temporary directories, after that nodeunit task runs our tests, and cleaning task removes the temporary directories. Grunt has grunt.file.mkdir() method, which creates a directory in the project library. We create a directory called generate, and within this directory we a test directory. After nodeunit task we run the cleaning task. This uses grunt.file.delete() method, and deletes all temporary directories.

Test whether there is a createFile function and if it gets a fileName parameter

We start our test file (file-test.js) with declaration of some variables:

//file-test.js  
var file = require("./file.js");  //our tested file  
var fs = require("fs");  //node.js file module  
var TEST_FILE = "generated/test/testFile.txt";  //we will create this file  
var CONTENT = "This is a simple text file.";  //this will be the contents of the file

Our first test in file-test.js looks like this:

exports.test_createFileMethodRequiresFileParameter = function(test){  
    test.throws(function(){
        file.createFile();
    });
    test.done();
};

Nodeunit has the throws() method, it expects block to throw an error. When we call this function without a parameter, it should throw an exception:

//file.js  
var fs = require("fs");

exports.createFile = function(file){  
    if (!file){
        throw "File name required";
    }       
};

Test if the createFile function gets a fileContent parameter

The same pattern works as the former test. In the test file:

exports.test_createFileMethodRequiresContentParameter = function(test){  
    test.throws(function(){
        file.createFile(TEST_FILE);
    });
    test.done();
};

And in the production code:

exports.createFile = function(file, content){  
    if (!file){
        throw "File name required";
    }
    if (!content){
        throw "File content required";
    }
};

Test if the file has been created

In the test we call createFile function, and check out if the file in question can be reached, more exactly the path of the file is valid. We use node.js realpathSync() method, it throws an exception if the path is not valid.

exports.test_fileCreated = function(test){  
  file.createFile(TEST_FILE, CONTENT);
  test.doesNotThrow(function(){
    console.log(fs.realpathSync(TEST_FILE, []) + " is a real path");
  });
  test.done();
};

It will generate an error, because the file has not been created yet. We can solve it with node.js writeFileSync() method:

exports.createFile = function(file, content){  
    if (!file){
      throw "File name required";
    }
    if (!content){
      throw "File content required";
    }
    fs.writeFileSync(file, content);
};

Okey, our createFile function is ready. Now let's see the other two functions.

Test if the file contains the fileContent (via readFile function)

In this test we call readFile() function, and test if the value of CONTENT variable and file content is equal.

exports.test_fileContent = function(test){  
  file.readFile(TEST_FILE, function(readContent){
    test.equals(CONTENT, readContent);
    test.done();
  });
};

It will fail, because we don't have readFile function yet. Fortunately, node.js has a readFile() method that can be used very easily:

exports.readFile = function(file, callback){  
    fs.readFile(file, function(err, data){
      if (err) {          
        throw err;
      }
      callback(data);
    });
  };

As the node.js documentation says, the callback is passed two arguments (err, data), where data is the contents of the file, so we can compare this with our CONTENT variable.

Test if the cleanUpFile function removes the file

This test is similar to the createFile test. We call cleanUpFile() function, and check the path of the file. We expect that this will throw an exception:

exports.test_cleanUpFile = function(test){  
  file.cleanUpFile(TEST_FILE);
  test.throws(function(){
    fs.realpathSync(TEST_FILE, []);
    });
  test.done();
};

The test fails, we need the cleanUpFile() function:

exports.cleanUpFile = function(file) {  
    if (fs.existsSync(file)) {
      fs.unlinkSync(file);
    }
    else {
      console.log("could not delete test file: [" + file + "]");
    }
  };

We use node.js existsSync() method, which tests if the file exists, and unlinkSync() method, which removes the file.

Summary

We used test driven approach to test some basic file operations, like creating, reading, and removing a file. Our tested functions actually are wrapper functions that call node.js methods, because we focused on TDD rather than re-creating these useful methods. We used Grunt for task management, and Nodeunit plugin for Grunt for testing.

You can download or clone this project from here.



comments powered by Disqus