-
-
Notifications
You must be signed in to change notification settings - Fork 708
Description
4.x.x Complete Migration Guide
Hello everyone! I'm writing this to serve as a guide for the 4.x.x
version.
To make this as complete as possible I'll include here every single new feature, breaking change and fix and their respective issues and Pull Requests. Bugs introduced and solved after the 3.5.0 released won't be here, since it does not matter for the new version.
I'll also add some examples on how to migrate old code or to use new features when talking about these kinds of changes.
Please let me know if I've forgotten anything 😄
Breaking Changes
-
We no longer support Node v0.10 and v0.12 (since their LTS has ended) (PRs: Drop support of node v0.10 #816, chore: update supported Node versions #901)
-
Instead of allowing the user to write the path of a property, now the deep flag performs a deep equality comparison when used with the
.property
assertion.
If you want the old behavior of using the dot or bracket notation to denote the property you want to assert against you can use the new.nested
flag. (Related Issues: Unexpected meaning ofdeep
flag inproperty
assertion #745, expect(array).to.not.include(obj) failing with constructors #743, PRs: Add.deep.property
for deep equality comparisons #758, Breaking: Rename.deep.property
to.nested.property
#757)const obj = {a: 1}; // The `.deep` flag now does deep equality comparisons expect({foo: obj}).to.have.deep.property('foo', {a: 1}); // Use the `nested` flag if you want to assert against a nested property using the bracket or dot notation expect({foo: obj}).to.have.nested.property('foo.a', 1); // You can also use both flags combined const obj2 = {a: {b: 2}}; expect({foo: obj2}).to.have.deep.nested.property('foo.a', {b: 2});
Please notice that the old methods which used the old behavior of the
deep
flag on theassert
interface have been renamed. They all have had thedeep
word changed by thenested
word. If you want to know more about this please take a look at Breaking: Rename.deep.property
to.nested.property
#757. -
Previously,
expect(obj).not.property(name, val)
would throw an Error ifobj
didn't have a property namedname
. This change causes the assertion to pass instead.
Theassert.propertyNotVal
andassert.deepPropertyNotVal
assertions were renamed toassert.notPropertyVal
andassert.notDeepPropertyVal
, respectively. (Related Issues: negated property(name, value) assertion #16, expect(array).to.not.include(obj) failing with constructors #743, Add.deep.property
for deep equality comparisons #758) -
You can now use the
deep
flag for the.include
assertion in order to perform adeep
equality check to see if something is included on thetarget
.
Previously,.include
was using strict equality (===
) for non-negated property inclusion, butdeep
equality for negated property inclusion and array inclusion.
This change causes the .include assertion to always use strict equality unless the deep flag is set.
Please take a look at this comment if you want to know more about it. (Related Issues: expect(array).to.not.include(obj) failing with constructors #743, PRs: Breaking: Fix.include
to always use strict equality #760, Add.deep.include
for deep equality comparisons #761)const obj = {a: 1}; expect([obj]).to.deep.include({a:1}); expect({foo: obj}).to.deep.include({foo: {a:1}});
-
Fix unstable behavior of the
NaN
assertion. Now we use the suggested ES6 implementation.
The new implementation is now more correct, strict and simple. While the old one threw false positives, the new implementation only checks if something isNaN
(or not if the.not
flag is used) and nothing else. (Related Issues: NaN assertion is not strict enough #498, NaN assertion is inconsistent with the standard and IEEE754 #682, void 0 and undefined is NaN #681, PRs: handle NaN properly in assertions #508)// Only `NaN` will be considered to be `NaN` and nothing else expect(NaN).to.be.NaN; // Anything that is not `NaN` cannot be considered as `NaN` expect('randomString').not.to.be.NaN; expect(true).not.to.be.NaN; expect({}).not.to.be.NaN; expect(4).not.to.be.NaN;
-
Throw when calling
_super
onoverwriteMethod
if the method being overwritten isundefined
.
Currently if the method you are trying to overwrite is not defined and your new method calls_super
it will throw anError
.(Related Issues: Overwrite method default _super does not throw #467, PRs: Throw when calling _super on overwriteMethod if method is undefined #528)
Before this change, calling_super
would simply returnthis
.// Considering the method `imaginaryMethod` does not exist, this would throw an error for example: chai.use(function (chai, utilities) { chai.Assertion.overwriteMethod('imaginaryMethod', function (_super) { return function () { _super.apply(this, arguments); } }); }); // This will throw an error since you are calling `_super` which should be a method (in this case, the overwritten assertion) that does not exist expect('anything').to.imaginaryMethod();
-
Now
showDiff
is turned on by default whenever theshowDiff
flag is anything other thanfalse
.
This issue will mostly affect plugin creators or anyone that made extensions to the core, since this affects theAssertion.assert
method. (Related Issues: New showDiff behaviour also depending on expected and actual values #574, PRs: No showDiff set on assert interface #515)// Now whenever you call `Assertion.assert` with anything that is not false for the `showDiff` flag it will be true // The assertion error that was thrown will have the `showDiff` flag turned on since it was not passed to the `assert` method try { new chai.Assertion().assert( 'one' === 'two' , 'expected #{this} to equal #{exp}' , 'expected #{this} to not equal #{act}' , 'one' , 'two' ); } catch(e) { assert.isTrue(e.showDiff); } // The assertion error that was thrown will have the `showDiff` flag turned off since here we passed `false` explicitly try { new chai.Assertion().assert( 'one' === 'two' , 'expected #{this} to equal #{exp}' , 'expected #{this} to not equal #{act}' , 'one' , 'two' , false ); } catch(e) { assert.isFalse(e.showDiff); }
-
The Typed Array types are now truncated if they're too long (in this case, if they exceed the
truncateThreshold
value on theconfig
). (Related Issues: Typed Arrays not truncated #441, PRs: Truncate Typed Arrays (#441) #576)var arr = []; for (var i = 1; i <= 1000; i++) { arr.push(i); } // The assertion below will truncate the diff shown and the enourmous typed array will be shown as: // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... ] instead of printing the whole typed array chai.expect(new Float32Array(100)).to.equal(1);
-
The assertions:
within
,above
,least
,below
,most
,increase
,decrease
will throw an error if the assertion's target or arguments are not numbers. (Related Issues: expect(null).to.be.within(0,10) does not fail assertion #691, PRs: [WIP] Verify types on within and related assertions #692, Verify types on increase | decrease #796)// These will throw errors, for example: expect(null).to.be.within(0, 1); expect(null).to.be.above(10); expect(null).to.be.at.least(20); expect(null).to.be.below(20); expect(null).to.be.at.most(20); expect(null).to.increase.by(20); expect(null).to.decrease.by(20); // This will not: expect('string').to.have.a.length.of.at.least(3);
-
Previously, expect(obj).not.ownProperty(name, val) would throw an Error if obj didn't have an own property (non-inherited) named name. This change causes the assertion to pass instead. (Related Issues: Refactor
.ownProperty
to leverage.property
#795, #, PRs: Breaking: Change.not.property(name, val)
behavior #744, RefactorownProperty
#810)*expect({ foo: 'baz' }).to.not.have.own.property('quux', 'baz');
-
The
.empty
assertion will now throw when it is passed non-string primitives and functions (PRs: Fix 'empty' assertion called on null #763, Make 'empty' throw on non-string primitives and functions #812)// These will throw TypeErrors: expect(Symbol()).to.be.empty; expect(function() {}).to.be.empty; expect(true).to.be.empty; expect(1).to.be.empty
-
Assertion subject (
obj
) changes when usingownProperty
orown.property
and thus enables chaining. (Related Issues: Change assertion subject when using ownProperty #281, PRs: Assertion subject (obj) changes when using ownProperty assertion #641)expect({val: 20}).to.have.own.property('val').above(10);
-
The
utils
(second argument passed to thechai.use
callback function) no longer exports thegetPathValue
function. If you want to use that please use thepathval
module, which is what chai uses internally now. (Related Issues: Chai Roadmap #457, Move pathval into its own module #737, PRs: Use external pathval module #830) -
The
.change
,.increase
, and.decrease
assertions changed from chainable method assertions to method assertions. They don't have any chaining behavior, and there's no generic semantic benefit to chaining them. (Related Issues:.change
,.increase
, and.decrease
wrong assertion type #917, PRs: fix: wrong assertion types #925)
// This will not work anymore because there is no benefit to chaining these assertions:
expect(function() {}).to.change.by(2)
expect(function() {}).to.increase.by(2)
expect(function() {}).to.decrease.by(2)
New Features
-
Throw when non-existent property is read. (Related Issues: Use ES6 Proxy to throw on non-existing assertions #407, Make proxies optional #766 PRs: Throw when non-existent property is read #721, Make proxies optional #770)
This is a potentially breaking change. Your build will fail if you have typos in your property assertions
Before4.x.x
when using property assertions they would not throw an error if you wrote it incorrectly.
The example below, for example, would pass:expect(true).to.be.ture; // Oops, typo, now Chai will throw an Error
Since this implementation depends on ES6
Proxies
it will only work on platforms that support it.This property can be enabled (default) or disabled through the
config.useProxy
property, for example:chai.config.useProxy = false; // disable use of Proxy
-
Add fix suggestions when accessing a nonexistent property in proxy mode. (Related Issues: Use levenshtein distance to suggest alternatives in proxy mode #771, PRs: Add fix suggestions when accessing a nonexistent property in proxy mode #782)
When a nonexistent property is accessed in proxy mode, Chai will compute the levenshtein distance to all possible properties in order to suggest the best fix to the user.expect(false).to.be.fals; // Error: Invalid Chai property: fals. Did you mean "false"? expect('foo').to.be.undefind; // Error: Invalid Chai property: undefind. Did you mean "undefined"? // If the Levenshtein distance between the word and any Chai property is greater than 4, no fix will be suggested expect('foo').to.be.fdsakfdsafsagsadgagsdfasf // error thrown, no fix suggested
-
When non-chainable methods (including overwritten non-chainable methods) are used incorrectly an error will be thrown with a helpful error message. (PRs: Wrap non-chainable methods in proxies #789)
expect(true).to.equal.true; // Invalid Chai property: equal.true. See docs for proper usage of "equal".
-
Add a new configuration setting that describes which keys will be ignored when checking for non-existing properties on an assertion before throwing an error.
Since this implementation depends on ES6Proxies
it will only work on platforms that support it. Also, if you disableconfig.useProxy
, this setting will have no effect. *(Related Issues: Cannot console.log assertions because of proxies #765, PRs: Configurable proxy blacklist #774)chai.config.proxyExcludedKeys.push('nonExistingProp'); expect('my string').to.nonExistingProp; // This won't throw an error now
-
Add script that registers should as a side-effect. (Related Issues: Add an ES6-friendly way to register "should" #594, Mocha hook to auto register should #693 PRs: Add script that registers should as a side-effect #604)
// You can now write: import 'chai/should'; // as opposed to: import {should} from 'chai'; should();
You can also register should via a
mocha
option:mocha --require chai/should
. -
The
change
assertion accepts a function as object. (Related Issues: Feature request: .change/.increases etc with a function for value #544, PRs: Change function for value #607)// Now you can also check if the return value of a function changes, for example assert.increases( someOperation, () => getSomething().length )
-
You can also assert for a delta using the
by
assertion alongside thechange
,increase
anddecrease
assertions. (Related Issues: Assert an increase/decrease by an amount (delta) #339, PRs: Change by delta (New Assertion) #621)
// You can use `.by` to assert the amount you want something to change
var obj = { val: 10 };
var increaseByTwo = function() { obj.val += 2 };
var decreaseByTwo = function() { obj.val -= 2 };
var increaseByThree = function() { obj.val += 3 };
expect(increaseByThree).to.change(obj, 'val').by(3);
expect(increaseByTwo).to.increase(obj, 'val').by(2);
expect(decreaseByTwo).to.decrease(obj, 'val').by(2);
// Please notice that if you want to assert something did change but not by some amount you need to use `.not` **after** the `change` related assertion
// Take a look at the examples below:
expect(increaseByThree).to.change(obj, 'val').but.not.by(5)
expect(increaseByTwo).to.increase(obj, 'val').but.not.by(1)
expect(decreaseByTwo).to.decrease(obj, 'val').but.not.by(1)
- The
.keys
assertion can now operate onmap
s andset
s. (Related Issues: Add assertions for ES6 Maps and Sets #632, PRs: Keys for maps and sets #633, Deep flag support for keys assertion #668)
// The `.keys` assertion now works on `map`s and `set`s natively, like the examples below:
expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.key({objKey: 'value'});
expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.any.keys([{objKey: 'value'}, {anotherKey: 'anotherValue'}]);
expect(new Map([['firstKey', 'firstValue'], [1, 2]])).to.contain.all.keys('firstKey', 1);
expect(new Set([['foo', 'bar'], ['example', 1]])).to.have.any.keys('foo');
// You can also use `.deep` when asserting agains `Map`s and `Set`s
expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.any.deep.keys([{objKey: 'value'}, {anotherKey: 'anotherValue'}]);
expect(new Map([['firstKey', 'firstValue'], [1, 2]])).to.contain.all.deep.keys('firstKey', 1);
expect(new Set([['foo', 'bar'], ['example', 1]])).to.have.any.deep.keys('foo');
-
Add compatibility with strict mode. (Related Issues: Error on strict mode #578, PRs: add compatibility with strict mode #665)
// This means you can now run your code with the `--use_strict` flag on Node // If want to understand more about this please read the issue related to this change
-
Add
does
andbut
as new no-op assertion. (Related Issues: Requesting "does" as a no-op assertion #700, Assert an increase/decrease by an amount (delta) #339 PRs: Change by delta (New Assertion) #621, Add .does as a no-op assertion, Fix #700 #701)// You can now write assertions forming phrases with these two new words: expect(increaseByThree).to.change(obj, 'val').but.not.by(5); expect(foobar).to.have.property("baz").which.does.not.have.property("thing");
-
Allow
use
to be imported using new ES6 module syntax. (Related Issues: "use" doesn't behave as expected as an ES6 named export #718, PRs: Fix chai.use as ES6 named export #724)// You can now import `use` using the ES6 module syntax, like the example below: import sinonChai from "sinon-chai"; import {expect, use} from "chai"; use(sinonChai);
You can also use
require
alongside the new ES6 destructuring feature:const sinonChai = require('sinon-chai'); const {expect, use} = require("chai"); use(sinonChai);
-
Add ordered flag for members assertion. (Related Issues: Add an assertion for same members where order is important #717, PRs: Add ordered flag for members assertions #728)
// You can now use the `ordered` flag to assert the order of elements when using the `members` assertion: expect([1, 2, 3]).to.include.ordered.members([1, 2]); // This passes expect([1, 2, 3]).to.include.ordered.members([2, 3]); // This will fail! Read the docs to know more.
-
Add
.own
flag to.property
assertion. It does the same thing as.ownProperty
and cannot be used alongisde the new.nested
flag. (Related Issues: Refactor.ownProperty
to leverage.property
#795, PRs: RefactorownProperty
#810)expect({a: 1}).to.have.own.property('a'); // The example below will thrown an Error expect({a: {b: 1}}).to.have.own.nested.property('a.b', 1);
-
Add
.deep
support to.property
assertion. (Related Issues: Refactor.ownProperty
to leverage.property
#795, PRs: RefactorownProperty
#810)expect({ foo: { bar: 'baz' } }).to.have.deep.own.property('foo', { bar: 'baz' }); expect({ foo: { bar: { baz: 'quux' } } }).to.have.deep.nested.property('foo.bar', { baz: 'quux' });
-
The
.empty
assertion will now work with ES6 collections (PRs: Fix 'empty' assertion called on null #763, Make 'empty' throw on non-string primitives and functions #812, Makeempty
assertion work with es6 collections #814)
Please notice that this assertion will throw an error when it is passed aWeakMap
orWeakSet
.expect(new Set()).to.be.empty; expect(new Map()).to.be.empty; // The examples below will throw a TypeError: expect(new WeakSet()).to.be.empty; expect(new WeakMap()).to.be.empty;
-
Add script that registers
should
as a side-effect. This change allows you to registershould
via a mocha option by using:mocha spec.js -r chai/register-should
and also allows you to register the testing style globally. (Issues: Mocha hook to auto register should #693, PRs: Add script that registers expect as a side-effect #868)require('chai/register-should'); // Using Should style
-
Add script that registers
assert
as a side-effect. This change allows you to registerassert
via a mocha option by using:mocha spec.js -r chai/register-assert
(Issues: Mocha hook to auto register should #693, PRs: Add script that registers expect as a side-effect #868, Auto register side effects and Update documentation #872)
require('chai/register-assert'); // Using Assert style
-
Add script that registers
expect
as a side-effect. This change allows you to registerexpect
via a mocha option by using:mocha spec.js -r chai/register-expect
(Issues: Mocha hook to auto register should #693, PRs: Add script that registers expect as a side-effect #868, Auto register side effects and Update documentation #872)require('chai/register-expect'); // Using Expect style
-
When the
length
assertion is chained directly off of an uninvoked method, it referencesfunction
's built-inlength
property instead of Chai'slength
assertion. This commit adds a guard to Chai methods to detect this problem and throw a helpful error message that advises the user on how to correct it. (Issues: .length(value) deprecation #684,length
as assertion =>lengthOf
#841, PRs: Un-deprecatelength
and add guard #897) -
Allows the
lockSsfi
flag to be set when creating new Assertion. This flag controls whether or not the givenssfi
flag should retain its current value, even as assertions are chained off of this object. This is usually set totrue
when creating a new assertion from within another assertion. It's also temporarily set totrue
before an overwritten assertion gets called by the overwriting assertion. (Issues: Question regarding stack trace. #878, Flag transfer problem in failed.include
assertion #904, PRs: Remove implementation frames from stack trace #922)// This will lock the stack stack function from this line on // The SSFI is the reference to the starting point for removing irrelevant frames from the stack trace new Assertion(obj, msg, ssfi, true).to.have.property('length')
-
The
nestedInclude
,deepNestedInclude
,ownInclude
anddeepOwnInclude
assertions and there negated pairs were added to theassert
interface. (Issues: Missing.nested.include
and.own.include
#905, PRs: assert: add nestedInclude, deepNestedInclude, ownInclude and deepOwnInclude #964)// '[]' and '.' in property names can be escaped using double backslashes. assert.nestedInclude({'.a': {'b': 'x'}}, {'\\.a.[b]': 'x'}); assert.notNestedInclude({'.a': {'b': 'x'}}, {'\\.a.b': 'y'}); assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {x: 1}}); assert.notDeepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {y: 1}}); assert.ownInclude({ a: 1 }, { a: 1 }); assert.notOwnInclude({ a: 1 }, { b: 2 }); assert.deepOwnInclude({a: {b: 2}}, {a: {b: 2}}); assert.notDeepOwnInclude({a: {b: 2}}, {a: {c: 3}});
Bug Fixes
- Fix missing msg argument for change related assertions. (Related Issues: None, PRs: Fix missing msg argument for change related assertions #606)
- The addMethod function returns a new assertion with flags copied over instead of
this
. (Related Issues:an
language chain affectslength
language chain? #562, .length(value) deprecation #684, chai-as-promised broken by change to addMethod and addProperty #723, PRs: addMethod returns a new assertion with flags copied over instead of this #642, Fix missing require() calls #660) - Fix stacktraces for overwritten properties and methods. (Related Issues: stack trace wrong for overridden properties #659, Fix stacktraces for overwritten properties and methods #661)
- Fix bug when testing Symbol equality with should syntax. (Related Issues: [ES6] should.equal fails for Symbols #669, PRs: Add ES6 Symbol support #672)
- Fix bug when asserting some valid ES6 keys. (Related Issues: assertKeys cannot handle some valid ES6 keys #674, PRs: Fix bug when asserting some valid ES6 keys #676)
- Fix bug caused when custom inspect method is used and this method calls stylize. (PRs: Fix failure when custom inspect method is used #680)
- Fix ownProperty on objects with no prototype. (Related Issues: Has own property on objects with nulll prototype #688, PRs: Fix ownProperty on objects with no prototype #689)
- Fix swapped expected and actual results for the
.members
assertion. (Related Issues: Fix wrong order of expected and actual values for members matcher #511, PRs: .members should show diff #702) - Fix
same.members
to properly handle duplicates by treating each one as a unique member. (Related Issues: Handling of duplicates when comparing sets #590, PRs: Fix duplicate handling in members assertion #739) - Fix deep.have.same.members() that was not printing a diff. (PRs: Cause deep.have.same.members() to print a diff. #509)
- Diff will be shown for assert's equal and notEqual methods. (Related Issues: Inconsistent between API guide and message from assert.equal #790, PRs: Enabling showDiff for assert's equal and notEqual methods. Closes #790 #794)
- The
overwriteMethod
,overwriteProperty
,addChainableMethod
,overwriteChainableMethod
functions will return new assertion with flags copied over instead of this. (Related Issues:an
language chain affectslength
language chain? #562, addMethod returns a new assertion with flags copied over instead of this #642, Consistency with returning new Assertion instead of this #791, PRs: Return new assertion with flags copied over instead of this. Fix #791 #799) - Proxy-related implementation frames were showing up in the stack traces for failed property assertions. Now we remove them by setting the proxy getter (instead of the property getter) as the starting point to remove all implementation frames. (PRs: Remove proxy frames from stack traces and improve docs/tests #884)
- Negated
keys
assertions will now consider size of sets. (Related Issues:.not.have.keys
incorrectly failing #919, PRs: fix: make negated.keys
consider size of sets #924) - 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 . (Related Issues: Sanity check for assertion instanceOf #893, PRs: fix: make negated
.keys
consider size of sets #924) - Fix all issues on expect/should interfaces that were causing implementation frames to leak through. Also improve the internal Chai error test helper to validate stack traces. (Issues: Question regarding stack trace. #878, Flag transfer problem in failed
.include
assertion #904, PRs: Remove implementation frames from stack trace #922) - This fixes custom messages not always being respected. (Issues: Remove implementation frames from stack trace #922, fix: always honor custom message #947, Flag transfer problem in failed
.include
assertion #904, PRs:.by
doesn't accept optionalmsg
parameter #916, Custom message not always respected #923) - Fix PhantomJS 1.x incompatibility (Issues: fix: PhantomJS 1.x incompatibility #966, PRs: Plugin issues with Chai v4 #890)