How to unit test logic inside node's FileSystem.readfile cb

869 Views Asked by At

Currently, I have a function that reads a file. When I throw and test for an error outside of the readfile callback, it works:

var doWork = function(path) {
    //throw new RangeError('blah'); // works, error is thrown

    fs.readFile(path, 'utf-8', function(error, data) {
        //etc.... logic.. etc..
        if(data.split('\n')[0] > x)
            throw new RangeError('blah'); //does not work
    });
}

My tests:

describe('my test suite', function(){
    it('should throw an error', function(){
        var func = function() {
            doWork('my path');
        }

        var err = new RangeError('blah');

        expect(func).to.throw(err); //no error is thrown if "throw" is inside readFile cb
    });
});

Results:

AssertionError: expected [Function: func] to throw RangeError
      at Context.<anonymous> (test.js:53:27)
2

There are 2 best solutions below

0
On

Going off @dm03514, set your file reader logic as a promise since you can't directly test throws asynchronously.

// fileprocessor.js
'use strict';

//imports
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));

module.exports = class FileProcessor {
    constructor() {

    }

    process(path) {
        var deferred = Promise.pending(); //like $q.deferred() in angular

        fs.readFileAsync(path, 'utf8').then(content => {
            //... etc... logic... etc...
            if(content.split('\n')[0] > 10) deferred.reject(new RangeError('bad'));

            //... etc.. more... logic
            deferred.resolve('good');
        }).catch(error => deferred.reject(error));

        return deferred.promise;
    }
}

Then in your test suites.

//test.js
'use strict';

var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var FileProcessor = require('./fileprocessor.js');

describe('my test suite', function(){

    var func = () => {
        return new FileProcessor().process('my path');
    }

    it('should resolve with a value', () => {
        return expect(func).should.eventually.equal('good');
    });

    it('should reject with an error', () => {
        return expect(func).should.be.rejectedWith(RangeError);
    });
});

Have a look at chai-as-promised: http://chaijs.com/plugins/chai-as-promised/

0
On

To handle errors asynchronously, you could use a callback, or promise, to notify the caller that an error occurs.

I think the issue is:

  • expect(func) is called
  • readFile yields (because it's async) back to the test
  • test reports a failure

You could change the call signature of doWork to accept a callback (conventionally passed error as a first argument and a result) as a second argument.


I personally would recommend looking into promises, as I think that they are much cleaner looking, and easier to understand/work with. The should allow you to continue to throw, and to register an catch/error event to handle the exception.