3

This works in Mocha/Chai:

describe('Chai throw() with old-style custom errors', ()=> {

  // old way
  function ES5Error(message = 'todo', value) {
    this.name = 'ES5Error';
    this.message = message;
  }
  ES5Error.prototype = new Error();

  it('catches ES5Errors', ()=> {
    var err = new ES5Error('This is a bad function.');
    var fn = function () { throw err; }
    expect(fn).to.throw(ES5Error);
    expect(fn).to.throw(Error);
    expect(fn).to.throw(/bad function/);
    expect(fn).to.not.throw('good function');
    expect(fn).to.throw(ES5Error, /bad function/);
    expect(fn).to.throw(err);
  });
});

While a class based approach does not:

describe('Chai throw() with new-style custom errors', ()=> {
  // New way
  class ExtendError extends Error {
    constructor(message = 'todo', value) {
      super(message);
      this.name = 'ExtendError';
      this.message = message;
    }
  }

  it('catches ExtendError', ()=> {
    var err = new ExtendError('This is a bad function.');
    var fn = function () { throw err; }
    expect(fn).to.throw(ExtendError);
    expect(fn).to.throw(Error);
    expect(fn).to.throw(/bad function/);
    expect(fn).to.not.throw('good function');
    expect(fn).to.throw(ExtendError, /bad function/);
    expect(fn).to.throw(err);
  });
});

I also implemented the related SO answer here. Though interesting, it still didn't work with mocha throws(). I'm basically happy to go with the ES5 style Errors, but I'm just unsure what the actual problem is. When I transpile the code for ExtendError I can't immediately see anything that would trip up the expect clause:

'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

var ExtendError = function (_Error) {
  _inherits(ExtendError, _Error);

  function ExtendError() {
    var message = arguments.length <= 0 || arguments[0] === undefined ? 'todo' : arguments[0];
    var value = arguments[1];

    _classCallCheck(this, ExtendError);

    var _this = _possibleConstructorReturn(this, _Error.call(this, message));

    _this.name = 'ExtendError';
    _this.message = message;
    return _this;
  }

  return ExtendError;
}(Error);

What is Mocha/Chai's problem?

Community
  • 1
  • 1
Ashley Coolman
  • 11,095
  • 5
  • 59
  • 81
  • @loganfsmyth Hm, is this marked as duplicate because https://www.npmjs.com/package/babel-plugin-transform-builtin-extend _should_ of worked? I did try your package, but it didn't fix things in this case (perhaps too many moving parts and I made a mistake) – Ashley Coolman Jun 24 '16 at 19:02
  • Not sure, that should fix it. I marked it as a dupe primarily because you're asking the same core question of "why isn't `instanceof` working here". – loganfsmyth Jun 24 '16 at 23:21
  • Ok thanks, I'll have another go and report back – Ashley Coolman Jun 24 '16 at 23:24

1 Answers1

5

The problem is not with Chai. If you do err instanceof ExtendError with your ES6 implementation you'll get false!

The ES5 and ES6 implementations of ExtendError you run are actually operating differently.

  • On the ES5 side, you call Error but do nothing with the return value. This is correct.

  • On the ES6 side, the super(...) call is converted to

    var _this = _possibleConstructorReturn(this, _Error.call(this, message));`
    

    and then _this takes the place of this in the original ES6 code. The return value of Error is used in the ES6 code and this is where everything goes to hell because the object you then return from the constructor is an Error object but not an ExtendError object.

I would just continue to use the ES5 syntax to derive from Error. I've tried a few approaches to keep the ES6 syntax but in the end they were hybrid or did awful awful things. The least objectionable method I came up with was:

class ExtendError {
    constructor(message = 'todo', value) {
        Error.call(this, message);
        this.name = 'ExtendError';
        this.message = message;
    }
}

ExtendError.prototype = Object.create(Error.prototype);
ExtendError.prototype.constructor = ExtendError;

It is not making great use of the ES6 class sugar...

Louis
  • 146,715
  • 28
  • 274
  • 320