Repository: chaijs/chai
Pull request #899 (2017-01-11 19:44:21)

Throw error when instanceof is passed something that is not a function as constructor
This aims to solve #893 and chaijs/get-func-name#20 is related to this issue.

Whenever passed something that is not an instance of a function, the `instanceof` assertion will now throw an error informing the user that he should pass a constructor to this assertion, but instead he has passed `<type>`

I also added tests for this in all three interfaces.

As I've said on the other PR an `instanceof` check is enough to determine whether or not something is a function, even though the `@@hasInstance` symbol exists.

Please let me know if I forgot anything.

------------------

EDIT: I have also started using the `typeof` check due to [this explanation](https://github.com/chaijs/get-func-name/pull/20#discussion_r95651819) by @shvaikalesh.


keithamus: Despite this throwing an error, I assume it's not a breaking change? Just makes the error message a user would receive more descriptive?
lucasfcosta: @keithamus It was already throwing an error before, which was:

`Right-hand side of 'instanceof' is not an object`

I think it's important to tell the user which type was passed in order for him to be able to fix what went wrong. Also, whenever passing `undefined` we had problems with `getName` due to trying to access the non-existing `name` property of undefined.
meeber: Agreed with @shvaikalesh. Whenever `constructor` has a `Symbol.hasInstance` property defined, this assertion should respect whatever `instanceof` returns, even if `constructor` isn't a function. However, the failed assertion message needs to be able to handle the fact that the return value of `getName` would be `null` in such a situation.

Example:

```js
var FakeConstructor = {};  // Not a function, but it has `Symbol.hasInstance` defined:
FakeConstructor[Symbol.hasInstance] = function (val) {
  return val === 3;
};

var fakeInstanceA = 3;

// Should pass even though FakeConstructor isn't a function
expect(fakeInstanceA).to.be.an.instanceof(FakeConstructor);

var fakeInstanceB = 4;

// Should fail because 4 !== 3, but what does failed assertion error message use as "name"?
expect(fakeInstanceB).to.be.an.instanceof(FakeConstructor);
```
shvaikalesh: Yet another thing to consider: if `constructor` have no `.prototype` (method or `=>`), `instanceof` will fail with `TypeError`. We can't simply check for `.prototype`, because non-generator bound functions won't have one, but would work with `instanceof` because of internal slots.
keithamus: Good points from @shvaikalesh - we should handle all these cases. I suppose we could just catch any `TypeError`s thrown by `instanceof` and make them friendlier?
lucasfcosta: @shvaikalesh I'm not sure I get it. Can't we just use `Object.getPrototypeOf` to see if something has a `prototype`? I've just tested this with both a `method` and an arrow function and it worked.

Can you please describe with more detail?

Also, thanks everyone for your feedback!
shvaikalesh: @lucasfcosta We can check for `.prototype` on function, however lack of it does not necessarily mean `instanceof` will wail: when function is bound with `Function#bind`, it is created without `.prototype`, but still works with `instanceof` because of `[[BoundTargetFunction]]`.


With all this, I agree with @keithamus that the most simple, reliable and future-compatible way is to wrap `instanceof` in `try`/`catch` and improve error message.
lucasfcosta: Thanks for the detailed explanation! Any suggestions on what the error message should be?

I was reading the specs and according to the algorithm I can only think of:

> The provided argument does not have a prototype.

But I think that's not explicit enough and I'm not sure it is a good idea to talk about internal implementation details on error messages.

Please let me know if you can think of anything better.

Also, if anyone wants to read about how `instanceof` works, [here is the link](https://tc39.github.io/ecma262/#sec-instanceofoperator).
shvaikalesh: `instanceof` may fail, if
+ `constructor` is not an Object
+ `@@hasInstance` getter threw
+ `constructor` has `!= null` and not callable `@@hasInstance`
+ `@@hasInstance` threw
+ `constructor` is not callable
+ `.prototype` is not an Object
+ `[[GetPrototypeOf]]` threw anywhere in prototype chain (proxies, ya know)

Many reasons here; we would have to reimplement `instanceof` to cover all of them. I believe `${instance} instanceof ${constructor} failed: ${originalErrorMessage}` is the way to go.
lucasfcosta: Well, we already cover some of these cases such in our `validInstanceOfTarget` check. That's why I thought of a more concise error message.

However, your suggestions look awesome. Thanks!
lucasfcosta: Hey friends, here it is. I also added a test to check if the error messages are being passed to our own error when `instanceof` throws.

Let me know if you have any further suggestions.
lucasfcosta: This has just been updated to include the changes you requested. Now everything should be working as expected and this also includes the new version of get-func-name, which has just been released.
meeber: Looks like Node v4 has failing tests. 
lucasfcosta: @meeber done!
This happened because Node v4 didn't implement `Symbol.hasInstance` but did implement `Symbol`. I added this new check before running tests on environment that do not support it.

Patch:
diff --git a/.travis.yml b/.travis.yml
index d26fa02..4c47383 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,9 +3,9 @@ language: node_js
 addons:
   sauce_connect: true
 node_js:
+ - 0.12 # to be removed 2016-12-31
  - 4 # to be removed 2018-04-01
  - 6 # to be removed 2019-04-01
- - 7 # to be removed 2017-06-30
  - lts/* # safety net; don't remove
  - node # safety net; don't remove
 
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..ddbd6be
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,6 @@
+keithamus
+lucasfcosta
+astorije
+meeber
+vieiralucas
+shvaikalesh
diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js
index 6b03e43..cae78d2 100644
--- a/lib/chai/core/assertions.js
+++ b/lib/chai/core/assertions.js
@@ -988,9 +988,29 @@ module.exports = function (chai, _) {
 
   function assertInstanceOf (constructor, msg) {
     if (msg) flag(this, 'message', msg);
+
+    var target = flag(this, 'object')
+    var validInstanceOfTarget = constructor === Object(constructor) && (
+        typeof constructor === 'function' ||
+        (typeof Symbol !== 'undefined' &&
+         typeof Symbol.hasInstance !== 'undefined' &&
+         Symbol.hasInstance in constructor)
+    );
+
+    if (!validInstanceOfTarget) {
+      var constructorType = constructor === null ? 'null' : typeof constructor;
+      throw new Error('The instanceof assertion needs a constructor but ' + constructorType + ' was given.');
+    }
+
+    var isInstanceOf = target instanceof constructor
+
     var name = _.getName(constructor);
+    if (name === null) {
+        name = 'an unnamed constructor';
+    }
+
     this.assert(
-        flag(this, 'object') instanceof constructor
+        isInstanceOf
       , 'expected #{this} to be an instance of ' + name
       , 'expected #{this} to not be an instance of ' + name
     );
@@ -2083,8 +2103,8 @@ module.exports = function (chai, _) {
     );
   }
 
-  Assertion.addMethod('change', assertChanges);
-  Assertion.addMethod('changes', assertChanges);
+  Assertion.addChainableMethod('change', assertChanges);
+  Assertion.addChainableMethod('changes', assertChanges);
 
   /**
    * ### .increase(target[, property[, message]])
@@ -2147,8 +2167,8 @@ module.exports = function (chai, _) {
     );
   }
 
-  Assertion.addMethod('increase', assertIncreases);
-  Assertion.addMethod('increases', assertIncreases);
+  Assertion.addChainableMethod('increase', assertIncreases);
+  Assertion.addChainableMethod('increases', assertIncreases);
 
   /**
    * ### .decrease(target[, property[, message]])
@@ -2211,8 +2231,8 @@ module.exports = function (chai, _) {
     );
   }
 
-  Assertion.addMethod('decrease', assertDecreases);
-  Assertion.addMethod('decreases', assertDecreases);
+  Assertion.addChainableMethod('decrease', assertDecreases);
+  Assertion.addChainableMethod('decreases', assertDecreases);
 
   /**
    * ### .by
diff --git a/lib/chai/utils/addChainableMethod.js b/lib/chai/utils/addChainableMethod.js
index 7cb4e49..a79a707 100644
--- a/lib/chai/utils/addChainableMethod.js
+++ b/lib/chai/utils/addChainableMethod.js
@@ -18,22 +18,20 @@ var transferFlags = require('./transferFlags');
  * Module variables
  */
 
-// Check whether `Object.setPrototypeOf` is supported
-var canSetPrototype = typeof Object.setPrototypeOf === 'function';
+// Check whether `__proto__` is supported
+var hasProtoSupport = '__proto__' in Object;
 
-// Without `Object.setPrototypeOf` support, this module will need to add properties to a function.
-// However, some of functions' own props are not configurable and should be skipped.
-var testFn = function() {};
-var excludeNames = Object.getOwnPropertyNames(testFn).filter(function(name) {
-  return !Object.getOwnPropertyDescriptor(testFn, name).configurable;
-});
+// Without `__proto__` support, this module will need to add properties to a function.
+// However, some Function.prototype methods cannot be overwritten,
+// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69).
+var excludeNames = /^(?:length|name|arguments|caller)$/;
 
 // Cache `Function` properties
 var call  = Function.prototype.call,
     apply = Function.prototype.apply;
 
 /**
- * ### .addChainableMethod(ctx, name, method, chainingBehavior)
+ * ### addChainableMethod (ctx, name, method, chainingBehavior)
  *
  * Adds a method to an object, such that the method can also be chained.
  *
@@ -91,6 +89,7 @@ module.exports = function addChainableMethod(ctx, name, method, chainingBehavior
           flag(this, 'ssfi', chainableMethodWrapper);
 
           var result = chainableBehavior.method.apply(this, arguments);
+
           if (result !== undefined) {
             return result;
           }
@@ -102,25 +101,22 @@ module.exports = function addChainableMethod(ctx, name, method, chainingBehavior
 
         addLengthGuard(chainableMethodWrapper, name, true);
 
-        // Use `Object.setPrototypeOf` if available
-        if (canSetPrototype) {
+        // Use `__proto__` if available
+        if (hasProtoSupport) {
           // Inherit all properties from the object by replacing the `Function` prototype
-          var prototype = Object.create(this);
+          var prototype = chainableMethodWrapper.__proto__ = Object.create(this);
           // Restore the `call` and `apply` methods from `Function`
           prototype.call = call;
           prototype.apply = apply;
-          Object.setPrototypeOf(chainableMethodWrapper, prototype);
         }
         // Otherwise, redefine all properties (slow!)
         else {
           var asserterNames = Object.getOwnPropertyNames(ctx);
           asserterNames.forEach(function (asserterName) {
-            if (excludeNames.indexOf(asserterName) !== -1) {
-              return;
+            if (!excludeNames.test(asserterName)) {
+              var pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
+              Object.defineProperty(chainableMethodWrapper, asserterName, pd);
             }
-
-            var pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
-            Object.defineProperty(chainableMethodWrapper, asserterName, pd);
           });
         }
 
diff --git a/lib/chai/utils/addLengthGuard.js b/lib/chai/utils/addLengthGuard.js
index 1f27e8b..f74c7a0 100644
--- a/lib/chai/utils/addLengthGuard.js
+++ b/lib/chai/utils/addLengthGuard.js
@@ -9,7 +9,7 @@ var fnLengthDesc = Object.getOwnPropertyDescriptor(function () {}, 'length');
  */
 
 /**
- * ### .addLengthGuard(fn, assertionName, isChainable)
+ * # addLengthGuard(fn, assertionName, isChainable)
  *
  * Define `length` as a getter on the given uninvoked method assertion. The
  * getter acts as a guard against chaining `length` directly off of an uninvoked
diff --git a/lib/chai/utils/addMethod.js b/lib/chai/utils/addMethod.js
index 56bd5b8..a5d3206 100644
--- a/lib/chai/utils/addMethod.js
+++ b/lib/chai/utils/addMethod.js
@@ -11,7 +11,7 @@ var proxify = require('./proxify');
 var transferFlags = require('./transferFlags');
 
 /**
- * ### .addMethod(ctx, name, method)
+ * ### .addMethod (ctx, name, method)
  *
  * Adds a method to the prototype of an object.
  *
diff --git a/lib/chai/utils/addProperty.js b/lib/chai/utils/addProperty.js
index d3757e6..f58400e 100644
--- a/lib/chai/utils/addProperty.js
+++ b/lib/chai/utils/addProperty.js
@@ -10,7 +10,7 @@ var isProxyEnabled = require('./isProxyEnabled');
 var transferFlags = require('./transferFlags');
 
 /**
- * ### .addProperty(ctx, name, getter)
+ * ### addProperty (ctx, name, getter)
  *
  * Adds a property to the prototype of an object.
  *
diff --git a/lib/chai/utils/compareByInspect.js b/lib/chai/utils/compareByInspect.js
index fde48d4..708ff28 100644
--- a/lib/chai/utils/compareByInspect.js
+++ b/lib/chai/utils/compareByInspect.js
@@ -11,7 +11,7 @@
 var inspect = require('./inspect');
 
 /**
- * ### .compareByInspect(mixed, mixed)
+ * ### .compareByInspect (mixed, mixed)
  *
  * To be used as a compareFunction with Array.prototype.sort. Compares elements
  * using inspect instead of default behavior of using toString so that Symbols
diff --git a/lib/chai/utils/expectTypes.js b/lib/chai/utils/expectTypes.js
index 48d842f..4b0bd45 100644
--- a/lib/chai/utils/expectTypes.js
+++ b/lib/chai/utils/expectTypes.js
@@ -5,7 +5,7 @@
  */
 
 /**
- * ### .expectTypes(obj, types)
+ * ### expectTypes(obj, types)
  *
  * Ensures that the object being tested against is of a valid type.
  *
diff --git a/lib/chai/utils/flag.js b/lib/chai/utils/flag.js
index dd53bfb..ffb48f0 100644
--- a/lib/chai/utils/flag.js
+++ b/lib/chai/utils/flag.js
@@ -5,7 +5,7 @@
  */
 
 /**
- * ### .flag(object, key, [value])
+ * ### flag(object, key, [value])
  *
  * Get or set a flag value on an object. If a
  * value is provided it will be set, else it will
diff --git a/lib/chai/utils/getActual.js b/lib/chai/utils/getActual.js
index 976e112..2b5b3fa 100644
--- a/lib/chai/utils/getActual.js
+++ b/lib/chai/utils/getActual.js
@@ -5,7 +5,7 @@
  */
 
 /**
- * ### .getActual(object, [actual])
+ * # getActual(object, [actual])
  *
  * Returns the `actual` value for an Assertion.
  *
diff --git a/lib/chai/utils/inspect.js b/lib/chai/utils/inspect.js
index af2cdb1..edbc55c 100644
--- a/lib/chai/utils/inspect.js
+++ b/lib/chai/utils/inspect.js
@@ -9,14 +9,12 @@ var config = require('../config');
 module.exports = inspect;
 
 /**
- * ### .inspect(obj, [showHidden], [depth], [colors])
- *
  * Echoes the value of a value. Tries to print the value out
  * in the best way possible given the different types.
  *
  * @param {Object} obj The object to print out.
  * @param {Boolean} showHidden Flag that shows hidden (not enumerable)
- *    properties of objects. Default is false.
+ *    properties of objects.
  * @param {Number} depth Depth in which to descend in object. Default is 2.
  * @param {Boolean} colors Flag to turn on ANSI escape codes to color the
  *    output. Default is false (no coloring).
diff --git a/lib/chai/utils/isNaN.js b/lib/chai/utils/isNaN.js
index d64f7f4..113256d 100644
--- a/lib/chai/utils/isNaN.js
+++ b/lib/chai/utils/isNaN.js
@@ -5,7 +5,7 @@
  */
 
 /**
- * ### .isNaN(value)
+ * ### isNaN(value)
  *
  * Checks if the given value is NaN or not.
  *
diff --git a/lib/chai/utils/isProxyEnabled.js b/lib/chai/utils/isProxyEnabled.js
index aa17dfa..f0ef041 100644
--- a/lib/chai/utils/isProxyEnabled.js
+++ b/lib/chai/utils/isProxyEnabled.js
@@ -7,7 +7,7 @@ var config = require('../config');
  */
 
 /**
- * ### .isProxyEnabled()
+ * # isProxyEnabled()
  *
  * Helper function to check if Chai's proxy protection feature is enabled. If
  * proxies are unsupported or disabled via the user's Chai config, then return
diff --git a/lib/chai/utils/objDisplay.js b/lib/chai/utils/objDisplay.js
index 32eacfa..d87d938 100644
--- a/lib/chai/utils/objDisplay.js
+++ b/lib/chai/utils/objDisplay.js
@@ -12,7 +12,7 @@ var inspect = require('./inspect');
 var config = require('../config');
 
 /**
- * ### .objDisplay(object)
+ * ### .objDisplay (object)
  *
  * Determines if an object or an array matches
  * criteria to be inspected in-line for error
diff --git a/lib/chai/utils/overwriteChainableMethod.js b/lib/chai/utils/overwriteChainableMethod.js
index 3ca9224..4d28f5a 100644
--- a/lib/chai/utils/overwriteChainableMethod.js
+++ b/lib/chai/utils/overwriteChainableMethod.js
@@ -8,7 +8,7 @@ var chai = require('../../chai');
 var transferFlags = require('./transferFlags');
 
 /**
- * ### .overwriteChainableMethod(ctx, name, method, chainingBehavior)
+ * ### overwriteChainableMethod (ctx, name, method, chainingBehavior)
  *
  * Overwites an already existing chainable method
  * and provides access to the previous function or
diff --git a/lib/chai/utils/overwriteMethod.js b/lib/chai/utils/overwriteMethod.js
index 004b85c..5d2306e 100644
--- a/lib/chai/utils/overwriteMethod.js
+++ b/lib/chai/utils/overwriteMethod.js
@@ -11,7 +11,7 @@ var proxify = require('./proxify');
 var transferFlags = require('./transferFlags');
 
 /**
- * ### .overwriteMethod(ctx, name, fn)
+ * ### overwriteMethod (ctx, name, fn)
  *
  * Overwites an already existing method and provides
  * access to previous function. Must return function
diff --git a/lib/chai/utils/overwriteProperty.js b/lib/chai/utils/overwriteProperty.js
index 73e6218..f988ced 100644
--- a/lib/chai/utils/overwriteProperty.js
+++ b/lib/chai/utils/overwriteProperty.js
@@ -10,7 +10,7 @@ var isProxyEnabled = require('./isProxyEnabled');
 var transferFlags = require('./transferFlags');
 
 /**
- * ### .overwriteProperty(ctx, name, fn)
+ * ### overwriteProperty (ctx, name, fn)
  *
  * Overwites an already existing property getter and provides
  * access to previous value. Must return function to use as getter.
diff --git a/lib/chai/utils/proxify.js b/lib/chai/utils/proxify.js
index 9ea8b7f..85c6cbf 100644
--- a/lib/chai/utils/proxify.js
+++ b/lib/chai/utils/proxify.js
@@ -10,7 +10,7 @@ var isProxyEnabled = require('./isProxyEnabled');
  */
 
 /**
- * ### .proxify(object)
+ * # proxify(object)
  *
  * Return a proxy of given object that throws an error when a non-existent
  * property is read. By default, the root cause is assumed to be a misspelled
diff --git a/lib/chai/utils/transferFlags.js b/lib/chai/utils/transferFlags.js
index 1668404..ea1316b 100644
--- a/lib/chai/utils/transferFlags.js
+++ b/lib/chai/utils/transferFlags.js
@@ -5,7 +5,7 @@
  */
 
 /**
- * ### .transferFlags(assertion, object, includeAll = true)
+ * ### transferFlags(assertion, object, includeAll = true)
  *
  * Transfer all the flags for `assertion` to `object`. If
  * `includeAll` is set to `false`, then the base Chai
diff --git a/package.json b/package.json
index a6bd6bc..ce2fe78 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "test": "make test"
   },
   "engines": {
-    "node": ">=4"
+    "node": ">=0.12"
   },
   "dependencies": {
     "assertion-error": "^1.0.1",


Test patch:
diff --git a/lib/chai/utils/test.js b/lib/chai/utils/test.js
index 6c4fc4a..4afc5fc 100644
--- a/lib/chai/utils/test.js
+++ b/lib/chai/utils/test.js
@@ -11,7 +11,7 @@
 var flag = require('./flag');
 
 /**
- * ### .test(object, expression)
+ * # test(object, expression)
  *
  * Test and object for expression.
  *
diff --git a/test/assert.js b/test/assert.js
index d8d3db0..d372fbc 100644
--- a/test/assert.js
+++ b/test/assert.js
@@ -141,6 +141,60 @@ describe('assert', function () {
     function Foo(){}
     assert.instanceOf(new Foo(), Foo);
 
+    err(function(){
+      assert.instanceOf(new Foo(), 1);
+    }, "The instanceof assertion needs a constructor but number was given.");
+
+    err(function(){
+      assert.instanceOf(new Foo(), 'batman');
+    }, "The instanceof assertion needs a constructor but string was given.");
+
+    err(function(){
+      assert.instanceOf(new Foo(), {});
+    }, "The instanceof assertion needs a constructor but object was given.");
+
+    err(function(){
+      assert.instanceOf(new Foo(), true);
+    }, "The instanceof assertion needs a constructor but boolean was given.");
+
+    err(function(){
+      assert.instanceOf(new Foo(), null);
+    }, "The instanceof assertion needs a constructor but null was given.");
+
+    err(function(){
+      assert.instanceOf(new Foo(), undefined);
+    }, "The instanceof assertion needs a constructor but undefined was given.");
+
+    var expectedError;
+    try {
+      t instanceof Thing;
+    } catch (err) {
+      errMsg = '[object Object] instanceof function Thing(){} failed: ' + err.message + '.';
+    }
+
+    err(function(){
+      function Thing(){};
+      var t = new Thing();
+      Thing.prototype = 1337;
+      assert.instanceOf(t, Thing);
+    }, expectedError);
+
+    if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') {
+        err(function(){
+          assert.instanceOf(new Foo(), Symbol());
+        }, "The instanceof assertion needs a constructor but symbol was given.");
+
+        err(function() {
+            var FakeConstructor = {};
+            var fakeInstanceB = 4;
+            FakeConstructor[Symbol.hasInstance] = function (val) {
+                return val === 3;
+            };
+
+            assert.instanceOf(fakeInstanceB, FakeConstructor);
+        }, 'expected 4 to be an instance of an unnamed constructor')
+    }
+
     err(function () {
       assert.instanceOf(5, Foo);
     }, "expected 5 to be an instance of Foo");
@@ -156,6 +210,47 @@ describe('assert', function () {
     function Foo(){}
     assert.notInstanceOf(new Foo(), String);
 
+    err(function(){
+      assert.notInstanceOf(new Foo(), 1);
+    }, "The instanceof assertion needs a constructor but number was given.");
+
+    err(function(){
+      assert.notInstanceOf(new Foo(), 'batman');
+    }, "The instanceof assertion needs a constructor but string was given.");
+
+    err(function(){
+      assert.notInstanceOf(new Foo(), {});
+    }, "The instanceof assertion needs a constructor but object was given.");
+
+    err(function(){
+      assert.notInstanceOf(new Foo(), true);
+    }, "The instanceof assertion needs a constructor but boolean was given.");
+
+    err(function(){
+      assert.notInstanceOf(new Foo(), null);
+    }, "The instanceof assertion needs a constructor but null was given.");
+
+    err(function(){
+      assert.notInstanceOf(new Foo(), undefined);
+    }, "The instanceof assertion needs a constructor but undefined was given.");
+
+
+    if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') {
+        err(function(){
+          assert.notInstanceOf(new Foo(), Symbol());
+        }, "The instanceof assertion needs a constructor but symbol was given.");
+
+        err(function() {
+            var FakeConstructor = {};
+            var fakeInstanceB = 4;
+            FakeConstructor[Symbol.hasInstance] = function (val) {
+                return val === 4;
+            };
+
+            assert.notInstanceOf(fakeInstanceB, FakeConstructor);
+        }, 'expected 4 to not be an instance of an unnamed constructor');
+    }
+
     err(function () {
       assert.notInstanceOf(new Foo(), Foo);
     }, "expected {} to not be an instance of Foo");
diff --git a/test/expect.js b/test/expect.js
index 3dd60b0..e5b3321 100644
--- a/test/expect.js
+++ b/test/expect.js
@@ -72,61 +72,61 @@ describe('expect', function () {
           expect(42).ok.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows overwritten property assertion', function () {
         err(function () {
           expect(42).tmpProperty.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled method assertion', function () {
         err(function () {
           expect(42).equal.pizza;
         }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".');
       });
-  
+
       it('throws when invalid property follows called method assertion', function () {
         err(function () {
           expect(42).equal(42).pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled overwritten method assertion', function () {
         err(function () {
           expect(42).tmpMethod.pizza;
         }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".');
       });
-  
+
       it('throws when invalid property follows called overwritten method assertion', function () {
         err(function () {
           expect(42).tmpMethod().pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled chainable method assertion', function () {
         err(function () {
           expect(42).a.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows called chainable method assertion', function () {
         err(function () {
           expect(42).a('number').pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled overwritten chainable method assertion', function () {
        err(function () {
           expect(42).tmpChainableMethod.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows called overwritten chainable method assertion', function () {
         err(function () {
           expect(42).tmpChainableMethod().pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('doesn\'t throw if invalid property is excluded via config', function () {
         expect(function () {
           expect(42).then;
@@ -366,6 +366,71 @@ describe('expect', function () {
     function Foo(){}
     expect(new Foo()).to.be.an.instanceof(Foo);
 
+    err(function(){
+      expect(new Foo()).to.an.instanceof(1);
+    }, "The instanceof assertion needs a constructor but number was given.");
+
+    err(function(){
+      expect(new Foo()).to.an.instanceof('batman');
+    }, "The instanceof assertion needs a constructor but string was given.");
+
+    err(function(){
+      expect(new Foo()).to.an.instanceof({});
+    }, "The instanceof assertion needs a constructor but object was given.");
+
+    err(function(){
+      expect(new Foo()).to.an.instanceof(true);
+    }, "The instanceof assertion needs a constructor but boolean was given.");
+
+    err(function(){
+      expect(new Foo()).to.an.instanceof(null);
+    }, "The instanceof assertion needs a constructor but null was given.");
+
+    err(function(){
+      expect(new Foo()).to.an.instanceof(undefined);
+    }, "The instanceof assertion needs a constructor but undefined was given.");
+
+    // Different browsers may have different error messages
+    var expectedError;
+    try {
+      t instanceof Thing;
+    } catch (err) {
+      errMsg = '[object Object] instanceof function Thing(){} failed: ' + err.message + '.';
+    }
+
+    err(function(){
+      function Thing(){};
+      var t = new Thing();
+      Thing.prototype = 1337;
+      expect(t).to.an.instanceof(Thing);
+    }, expectedError)
+
+    if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') {
+        err(function(){
+          expect(new Foo()).to.an.instanceof(Symbol());
+        }, "The instanceof assertion needs a constructor but symbol was given.");
+
+        err(function() {
+            var FakeConstructor = {};
+            var fakeInstanceB = 4;
+            FakeConstructor[Symbol.hasInstance] = function (val) {
+                return val === 3;
+            };
+
+            expect(fakeInstanceB).to.be.an.instanceof(FakeConstructor);
+        }, 'expected 4 to be an instance of an unnamed constructor')
+
+        err(function() {
+            var FakeConstructor = {};
+            var fakeInstanceB = 4;
+            FakeConstructor[Symbol.hasInstance] = function (val) {
+                return val === 4;
+            };
+
+            expect(fakeInstanceB).to.not.be.an.instanceof(FakeConstructor);
+        }, 'expected 4 to not be an instance of an unnamed constructor')
+    }
+
     err(function(){
       expect(3).to.an.instanceof(Foo, 'blah');
     }, "blah: expected 3 to be an instance of Foo");
@@ -1431,8 +1496,6 @@ describe('expect', function () {
     expect(['foo', 'bar']).to.not.include('baz');
     expect(['foo', 'bar']).to.not.include(1);
 
-    expect({a: 1}).to.include({'toString': Object.prototype.toString});
-
     var obj1 = {a: 1}
       , obj2 = {b: 2};
     expect([obj1, obj2]).to.include(obj1);
@@ -1549,91 +1612,6 @@ describe('expect', function () {
     }, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
   });
 
-  it('nested.include()', function () {
-    expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'});
-    expect({a: {b: ['x', 'y']}}).to.not.nested.include({'a.b[1]': 'x'});
-    expect({a: {b: ['x', 'y']}}).to.not.nested.include({'a.c': 'y'});
-
-    expect({a: {b: [{x: 1}]}}).to.not.nested.include({'a.b[0]': {x: 1}});
-
-    expect({'.a': {'[b]': 'x'}}).to.nested.include({'\\.a.\\[b\\]': 'x'});
-    expect({'.a': {'[b]': 'x'}}).to.not.nested.include({'\\.a.\\[b\\]': 'y'});
-
-    err(function () {
-      expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'x'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.b[1]' of 'x', but got 'y'");
-
-    err(function () {
-      expect({a: {b: ['x', 'y']}}).to.nested.include({'a.c': 'y'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.c'");
-
-    err(function () {
-      expect({a: {b: ['x', 'y']}}).to.not.nested.include({'a.b[1]': 'y'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
-  });
-
-  it('deep.nested.include()', function () {
-    expect({a: {b: [{x: 1}]}}).to.deep.nested.include({'a.b[0]': {x: 1}});
-    expect({a: {b: [{x: 1}]}}).to.not.deep.nested.include({'a.b[0]': {y: 2}});
-    expect({a: {b: [{x: 1}]}}).to.not.deep.nested.include({'a.c': {x: 1}});
-
-    expect({'.a': {'[b]': {x: 1}}})
-      .to.deep.nested.include({'\\.a.\\[b\\]': {x: 1}});
-    expect({'.a': {'[b]': {x: 1}}})
-      .to.not.deep.nested.include({'\\.a.\\[b\\]': {y: 2}});
-
-    err(function () {
-      expect({a: {b: [{x: 1}]}}).to.deep.nested.include({'a.b[0]': {y: 2}});
-    }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.b[0]' of { y: 2 }, but got { x: 1 }");
-
-    err(function () {
-      expect({a: {b: [{x: 1}]}}).to.deep.nested.include({'a.c': {x: 1}});
-    }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.c'");
-
-    err(function () {
-      expect({a: {b: [{x: 1}]}}).to.not.deep.nested.include({'a.b[0]': {x: 1}});
-    }, "expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
-  });
-
-  it('own.include()', function () {
-    expect({a: 1}).to.own.include({a: 1});
-    expect({a: 1}).to.not.own.include({a: 3});
-    expect({a: 1}).to.not.own.include({'toString': Object.prototype.toString});
-
-    expect({a: {b: 2}}).to.not.own.include({a: {b: 2}});
-
-    err(function () {
-      expect({a: 1}).to.own.include({a: 3});
-    }, "expected { a: 1 } to have own property 'a' of 3, but got 1");
-
-    err(function () {
-      expect({a: 1}).to.own.include({'toString': Object.prototype.toString});
-    }, "expected { a: 1 } to have own property 'toString'");
-
-    err(function () {
-      expect({a: 1}).to.not.own.include({a: 1});
-    }, "expected { a: 1 } to not have own property 'a' of 1");
-  });
-
-  it('deep.own.include()', function () {
-    expect({a: {b: 2}}).to.deep.own.include({a: {b: 2}});
-    expect({a: {b: 2}}).to.not.deep.own.include({a: {c: 3}});
-    expect({a: {b: 2}})
-      .to.not.deep.own.include({'toString': Object.prototype.toString});
-
-    err(function () {
-      expect({a: {b: 2}}).to.deep.own.include({a: {c: 3}});
-    }, "expected { a: { b: 2 } } to have deep own property 'a' of { c: 3 }, but got { b: 2 }");
-
-    err(function () {
-      expect({a: {b: 2}}).to.deep.own.include({'toString': Object.prototype.toString});
-    }, "expected { a: { b: 2 } } to have deep own property 'toString'");
-
-    err(function () {
-      expect({a: {b: 2}}).to.not.deep.own.include({a: {b: 2}});
-    }, "expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
-  });
-
   it('keys(array|Object|arguments)', function(){
     expect({ foo: 1 }).to.have.keys(['foo']);
     expect({ foo: 1 }).have.keys({ 'foo': 6 });
@@ -1913,7 +1891,7 @@ describe('expect', function () {
       expect(testSet).to.not.have.any.deep.keys([{13: 37}, 'thisDoesNotExist', 'thisToo']);
       expect(testSet).to.not.have.any.deep.keys([20, 1, {13: 37}]);
       expect(testSet).to.not.have.all.deep.keys([{thisIs: 'anExampleObject'}, {'iDoNot': 'exist'}]);
- 
+
       var weirdSetKey1 = Object.create(null)
         , weirdSetKey2 = {toString: NaN}
         , weirdSetKey3 = []
diff --git a/test/should.js b/test/should.js
index a0a6d4d..fad0657 100644
--- a/test/should.js
+++ b/test/should.js
@@ -51,79 +51,79 @@ describe('should', function() {
 
     describe('proxify', function () {
       if (typeof Proxy === 'undefined' || typeof Reflect === 'undefined') return;
-  
+
       it('throws when invalid property follows should', function () {
         err(function () {
           (42).should.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows language chain', function () {
         err(function () {
           (42).should.to.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows property assertion', function () {
         err(function () {
           (42).should.ok.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows overwritten property assertion', function () {
         err(function () {
           (42).should.tmpProperty.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled method assertion', function () {
         err(function () {
           (42).should.equal.pizza;
         }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".');
       });
-  
+
       it('throws when invalid property follows called method assertion', function () {
         err(function () {
           (42).should.equal(42).pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled overwritten method assertion', function () {
         err(function () {
           (42).should.tmpMethod.pizza;
         }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".');
       });
-  
+
       it('throws when invalid property follows called overwritten method assertion', function () {
         err(function () {
           (42).should.tmpMethod().pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled chainable method assertion', function () {
         err(function () {
           (42).should.a.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows called chainable method assertion', function () {
         err(function () {
           (42).should.a('number').pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows uncalled overwritten chainable method assertion', function () {
         err(function () {
           (42).should.tmpChainableMethod.pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('throws when invalid property follows called overwritten chainable method assertion', function () {
         err(function () {
           (42).should.tmpChainableMethod().pizza;
         }, 'Invalid Chai property: pizza');
       });
-  
+
       it('doesn\'t throw if invalid property is excluded via config', function () {
         (function () {
           (42).should.then;
@@ -416,6 +416,71 @@ describe('should', function() {
     function Foo(){}
     new Foo().should.be.an.instanceof(Foo);
 
+    err(function(){
+      new Foo().should.be.an.instanceof(1);
+    }, "The instanceof assertion needs a constructor but number was given.");
+
+    err(function(){
+      new Foo().should.be.an.instanceof('batman');
+    }, "The instanceof assertion needs a constructor but string was given.");
+
+    err(function(){
+      new Foo().should.be.an.instanceof({});
+    }, "The instanceof assertion needs a constructor but object was given.");
+
+    err(function(){
+      new Foo().should.be.an.instanceof(true);
+    }, "The instanceof assertion needs a constructor but boolean was given.");
+
+    err(function(){
+      new Foo().should.be.an.instanceof(null);
+    }, "The instanceof assertion needs a constructor but null was given.");
+
+    err(function(){
+      new Foo().should.be.an.instanceof(undefined);
+    }, "The instanceof assertion needs a constructor but undefined was given.");
+
+    // Different browsers may have different error messages
+    var expectedError;
+    try {
+      t instanceof Thing;
+    } catch (err) {
+      errMsg = '[object Object] instanceof function Thing(){} failed: ' + err.message + '.';
+    }
+
+    err(function(){
+      function Thing(){};
+      var t = new Thing();
+      Thing.prototype = 1337;
+      t.should.be.an.instanceof(Thing);
+    }, expectedError);
+
+    if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') {
+        err(function(){
+          new Foo().should.be.an.instanceof(Symbol());
+        }, "The instanceof assertion needs a constructor but symbol was given.");
+
+        err(function() {
+            var FakeConstructor = {};
+            var fakeInstanceB = 4;
+            FakeConstructor[Symbol.hasInstance] = function (val) {
+                return val === 3;
+            };
+
+            fakeInstanceB.should.be.an.instanceof(FakeConstructor);
+        }, 'expected 4 to be an instance of an unnamed constructor');
+
+        err(function() {
+            var FakeConstructor = {};
+            var fakeInstanceB = 4;
+            FakeConstructor[Symbol.hasInstance] = function (val) {
+                return val === 4;
+            };
+
+            fakeInstanceB.should.not.be.an.instanceof(FakeConstructor);
+        }, 'expected 4 to not be an instance of an unnamed constructor');
+    }
+
     err(function(){
       (3).should.an.instanceof(Foo, 'blah');
     }, "blah: expected 3 to be an instance of Foo");
@@ -1251,8 +1316,6 @@ describe('should', function() {
     ['foo', 'bar'].should.not.include('baz');
     ['foo', 'bar'].should.not.include(1);
 
-    ({a: 1}).should.include({'toString': Object.prototype.toString});
-
     var obj1 = {a: 1}
       , obj2 = {b: 2};
     [obj1, obj2].should.include(obj1);
@@ -1349,91 +1412,6 @@ describe('should', function() {
     }, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
   });
 
-  it('nested.include()', function () {
-    ({a: {b: ['x', 'y']}}).should.nested.include({'a.b[1]': 'y'});
-    ({a: {b: ['x', 'y']}}).should.not.nested.include({'a.b[1]': 'x'});
-    ({a: {b: ['x', 'y']}}).should.not.nested.include({'a.c': 'y'});
-
-    ({a: {b: [{x: 1}]}}).should.not.nested.include({'a.b[0]': {x: 1}});
-
-    ({'.a': {'[b]': 'x'}}).should.nested.include({'\\.a.\\[b\\]': 'x'});
-    ({'.a': {'[b]': 'x'}}).should.not.nested.include({'\\.a.\\[b\\]': 'y'});
-
-    err(function () {
-      ({a: {b: ['x', 'y']}}).should.nested.include({'a.b[1]': 'x'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.b[1]' of 'x', but got 'y'");
-
-    err(function () {
-      ({a: {b: ['x', 'y']}}).should.nested.include({'a.c': 'y'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.c'");
-
-    err(function () {
-      ({a: {b: ['x', 'y']}}).should.not.nested.include({'a.b[1]': 'y'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
-  });
-
-  it('deep.nested.include()', function () {
-    ({a: {b: [{x: 1}]}}).should.deep.nested.include({'a.b[0]': {x: 1}});
-    ({a: {b: [{x: 1}]}}).should.not.deep.nested.include({'a.b[0]': {y: 2}});
-    ({a: {b: [{x: 1}]}}).should.not.deep.nested.include({'a.c': {x: 1}});
-
-    ({'.a': {'[b]': {x: 1}}})
-      .should.deep.nested.include({'\\.a.\\[b\\]': {x: 1}});
-    ({'.a': {'[b]': {x: 1}}})
-      .should.not.deep.nested.include({'\\.a.\\[b\\]': {y: 2}});
-
-    err(function () {
-      ({a: {b: [{x: 1}]}}).should.deep.nested.include({'a.b[0]': {y: 2}});
-    }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.b[0]' of { y: 2 }, but got { x: 1 }");
-
-    err(function () {
-      ({a: {b: [{x: 1}]}}).should.deep.nested.include({'a.c': {x: 1}});
-    }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.c'");
-
-    err(function () {
-      ({a: {b: [{x: 1}]}}).should.not.deep.nested.include({'a.b[0]': {x: 1}});
-    }, "expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
-  });
-
-  it('own.include()', function () {
-    ({a: 1}).should.own.include({a: 1});
-    ({a: 1}).should.not.own.include({a: 3});
-    ({a: 1}).should.not.own.include({'toString': Object.prototype.toString});
-
-    ({a: {b: 2}}).should.not.own.include({a: {b: 2}});
-
-    err(function () {
-      ({a: 1}).should.own.include({a: 3});
-    }, "expected { a: 1 } to have own property 'a' of 3, but got 1");
-
-    err(function () {
-      ({a: 1}).should.own.include({'toString': Object.prototype.toString});
-    }, "expected { a: 1 } to have own property 'toString'");
-
-    err(function () {
-      ({a: 1}).should.not.own.include({a: 1});
-    }, "expected { a: 1 } to not have own property 'a' of 1");
-  });
-
-  it('deep.own.include()', function () {
-    ({a: {b: 2}}).should.deep.own.include({a: {b: 2}});
-    ({a: {b: 2}}).should.not.deep.own.include({a: {c: 3}});
-    ({a: {b: 2}})
-      .should.not.deep.own.include({'toString': Object.prototype.toString});
-
-    err(function () {
-      ({a: {b: 2}}).should.deep.own.include({a: {c: 3}});
-    }, "expected { a: { b: 2 } } to have deep own property 'a' of { c: 3 }, but got { b: 2 }");
-
-    err(function () {
-      ({a: {b: 2}}).should.deep.own.include({'toString': Object.prototype.toString});
-    }, "expected { a: { b: 2 } } to have deep own property 'toString'");
-
-    err(function () {
-      ({a: {b: 2}}).should.not.deep.own.include({a: {b: 2}});
-    }, "expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
-  });
-
   it('keys(array|Object|arguments)', function(){
     ({ foo: 1 }).should.have.keys(['foo']);
     ({ foo: 1 }).should.have.keys({ 'foo': 6 });
diff --git a/test/utilities.js b/test/utilities.js
index c2399c8..98a30d3 100644
--- a/test/utilities.js
+++ b/test/utilities.js
@@ -883,7 +883,8 @@ describe('utilities', function () {
       // Ensure that foo returns an Assertion (not a function)
       expect(expect('x').x()).to.be.an.instanceOf(assertionConstructor);
 
-      if (typeof Object.setPrototypeOf === 'function') {
+      var hasProtoSupport = '__proto__' in Object;
+      if (hasProtoSupport) {
         expect(expect('x').x).to.be.an.instanceOf(assertionConstructor);
       }
     });