In order to illustrate how to unit test with private functions correctly, I’ll be using the following code that attempts to delete a temporary markdown file.

NOTE: The messaging module is a simple module that writes standard messages out to the console. I’ve kept all user-facing messages in a single file in order to increase maintainability. The messages aren’t important for this demo and they will be stubbed in our unit tests anyway.

var fs = require('fs');
var path = require('path');
var messaging = require('./messaging');


module.exports = {
    /**
     * Deletes the `temppdf.md` file.
     * 
     * Deletes the temporary PDF markdown file.
     * 
     * @returns {boolean}       Returns true if the file is was successfully deleted (or non-existent). Returns false if the file cannot be deleted (e.g. file lock, etc.).
     */
    deleteTempPdf: async function () {

        let tempPdf = path.resolve('temppdf.md');
        let stat = await checkFileAccess(tempPdf);

        if (stat == 0) {
            messaging.printTempPdfMessage(0);
            return true;
        } else if (stat == 1) {
            messaging.printTempPdfMessage(1);
            return false;
        } else if (stat == 2) {
            // File is writable (e.g. no lock), attempt to delete
            fs.unlinkSync(tempPdf);

            // Check file status again
            stat = await checkFileAccess(tempPdf);

            if (stat == 0) {
                messaging.printTempPdfMessage(2);
                return true;
            } else {
                messaging.printTempPdfMessage(3);
                return false;
            }
        } else {
            messaging.printTempPdfMessage(4);
            return false;
        }
    }
}

/**
 * Checks file access.
 * 
 * Checks a given file for access on the filesystem.
 * 
 * @param {string} file         Path of the file.
 * 
 * @return {number}             0 if file doesn't exist; 1 if file is readonly; 2 if file is writable
 */
 function checkFileAccess(file) {
    return new Promise((resolve) => {
        fs.access(file, fs.constants.F_OK | fs.constants.W_OK, (err) => {
            if (err) {
                if (err.code === 'ENOENT') {
                    resolve(0);
                } else {
                    resolve(1);
                }
            } else {
                resolve(2);
            }
        });
    });
}

What you see here are two methods – one public and one private – deleteTempPdf and checkFileAccess. The checkFileAccess uses the fs library to check file access conditions.

NOTE: fs.exists has been deprecated and, therefore, I’m using fs.access.

As you can see form the JSDoc description, `checkFileAccess` returns one of three possible values: ‘0’ if the file doesn’t exist; ‘1’ if the file is readonly (e.g. possible file lock, or current permissions don’t allow write access); or, ‘2’ if the file does exist and is writable.

Based on these return values, deleteTempPdf will attempt to perform the necessary operation(s). If the file doesn’t exist or does exist, but is deleted successfully, deleteTempPdf will return true for a successful deletion. Otherwise, deleteTempPdf will return false, meaning the file cannot be deleted.

Looking at the if-then structure of deleteTempPdf the logic is as follows:

  1. If the file doesn’t exist, return true
  2. Else, if the file is readonly, return false
  3. Else, if the file does exist, but is writable:
    1. Attempt to delete the file, then check the file again
    2. If the file doesn’t exist (deleted successfully), return true
    3. Otherwise (the file wasn’t deleted successfully for whatever reason), return false
  4. Else (unknown issue), return false

With these five branches (note that points 3 and 3.1 are simply steps in the process and we only need to test 3.2 and 3.3), we can identify the five unit tests we need to write:

  1. it('should return true for "temppdf.md" not existing')
  2. it('should return false for "temppdf.md" being readonly')
  3. it('should return true for "temppdf.md" existing, being writable and being deleted successfully')
  4. it('should return false for "temppdf.md" existing, being writable, but not deleted successfully')
  5. it('should return false for unknown error when checking access of "temppdf.md"')

The initial barebones version of our unit test spec file should look like the following:

describe('tempFile', () => {
    describe('deleteTempPdf', () => {
        it('should return true for "temppdf.md" not existing', () => {

        })

        it('should return false for "temppdf.md" being readonly', () => {

        })

        it('should return true for "temppdf.md" existing, being writable and being deleted successfully', () => {

        })

        it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => {

        })

        it('should return false for unknown error when checking access of "temppdf.md"', () => {

        })
    })
})

Upon successfully writing all five unit tests, we’ll achieve 100% code coverage.

On the next page, we’ll start filling in these unit tests.

Like What You See?

Subscribe to receive new posts in your inbox.

Privacy Preference Center