|
| 1 | +--- |
| 2 | +title: Migration |
| 3 | +template: default.ejs |
| 4 | +menuPage: false |
| 5 | +theme: light |
| 6 | +repository: https://github.com/unexpectedjs/unexpected |
| 7 | +--- |
| 8 | + |
| 9 | +# Migration |
| 10 | + |
| 11 | +Unexpected has from its inception taken backwards compatibility very |
| 12 | +seriously. We think this is particularly important for a library |
| 13 | +intended for use in testing – we recognise that comprehensive test |
| 14 | +suites are often a large investment and play a central role in ongoing |
| 15 | +development of software. This sets a very high bar for any changes. |
| 16 | + |
| 17 | +The project strictly adheres to semver. As such anything that carries |
| 18 | +a risk of breakage is carefully considered, must demonstrate significant |
| 19 | +benefit to be chosen for inclusion and would mean a new major version. |
| 20 | + |
| 21 | +## Major revisions |
| 22 | + |
| 23 | +### Migration to Unexpected 11 |
| 24 | + |
| 25 | +Version 11 is the first major release that makes some carefully |
| 26 | +calculated changes in the user facing API which may require test |
| 27 | +changes. We have made every effort to minimise the effects on end |
| 28 | +users and this extends to the plugin ecosystem which has seen the |
| 29 | +most commonly used being made compatible. |
| 30 | + |
| 31 | +The changes are all focused on simplifying the mental model for users. |
| 32 | +A handful of constructs which proved themselves to be sources of |
| 33 | +confusion and to the best of our knowledge have not seen wide use |
| 34 | +have been replaced with **existing** alternatives. This means a safe |
| 35 | +upgrade path is available: tests can be updated against the current |
| 36 | +version of the library and the changes are forwards compatible. |
| 37 | + |
| 38 | +#### Raise minimum node version to 6+ |
| 39 | + |
| 40 | +In our work to continue moving forward we have upgraded many of our |
| 41 | +dependencies. Many of these tools have themselves dropped node 4 |
| 42 | +support and we have decided to do the same. This does not affect our |
| 43 | +browser compatibility which remains at the ES5 level and IE11. |
| 44 | + |
| 45 | +#### Using extensions via the API requires calling .clone() |
| 46 | + |
| 47 | +Previously once imported the entirety of the Unexpected API was |
| 48 | +immediately available to users which could lead to surprising |
| 49 | +results if types or assertions were added to it directly. |
| 50 | + |
| 51 | +In version 11 the top-level of the library has been frozen and |
| 52 | +extending the functionality requires an expilcit `.clone()` call |
| 53 | +to be made: |
| 54 | + |
| 55 | +```js#evaluate:false |
| 56 | +const unexpected = require('unexpected'); |
| 57 | +
|
| 58 | +const expect = unexpected.clone(); |
| 59 | +
|
| 60 | +expect.addAssertion(...); |
| 61 | +``` |
| 62 | + |
| 63 | +#### Use `expect.it()` for assertions on property values |
| 64 | + |
| 65 | +Previously when used in conjunction with [to satisfy](../assertions/any/to-satisfy/) |
| 66 | +a property |
| 67 | +defined as a function on the right-hand side would be passed the |
| 68 | +value to allow further assertions: |
| 69 | + |
| 70 | +```js#evaluate:false |
| 71 | +const obj = { |
| 72 | + version: 11, |
| 73 | + greeting: 'hello new major' |
| 74 | +}; |
| 75 | +
|
| 76 | +expect(obj, 'to satisfy', { |
| 77 | + year: 2018, |
| 78 | + greeting: function(theValue) { |
| 79 | + expect(theValue, 'to start with', 'hello'); |
| 80 | + } |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +> Note: this is no longer supported by Unexpected v11 |
| 85 | +
|
| 86 | +For cases where the value of a property must conform to a more |
| 87 | +rigorous set of constraints, we replaced the earlier syntax with |
| 88 | +the `expect.it()`: |
| 89 | + |
| 90 | +```js |
| 91 | +const obj = { |
| 92 | + version: 11, |
| 93 | + greeting: 'hello new major' |
| 94 | +}; |
| 95 | + |
| 96 | +expect(obj, 'to satisfy', { |
| 97 | + version: 11, |
| 98 | + greeting: expect.it(function(theValue) { |
| 99 | + expect(theValue, 'to start with', 'hello'); |
| 100 | + }) |
| 101 | +}); |
| 102 | +``` |
| 103 | + |
| 104 | +We believe this is also much more versatile because of the powerful |
| 105 | +chaining support provided by `expect.it()`: |
| 106 | + |
| 107 | +```js |
| 108 | +expect(obj, 'to satisfy', { |
| 109 | + version: 11, |
| 110 | + greeting: expect |
| 111 | + .it('to be a string') |
| 112 | + .and('to end with', 'major') |
| 113 | + .and(theValue => expect(theValue.split(' '), 'to have length', 3)) |
| 114 | +}); |
| 115 | +``` |
| 116 | + |
| 117 | +#### Functions are always compared by value |
| 118 | + |
| 119 | +Building upon the `expect.it()` changes, all function comparisons |
| 120 | +will now be made using the identity of the function. This is unlike |
| 121 | +previous versions where they were treated specially and could lead |
| 122 | +to surprising results: |
| 123 | + |
| 124 | +```js |
| 125 | +function createErrorIfRequired(message) { |
| 126 | + if (typeof message !== 'string') { |
| 127 | + return null; |
| 128 | + } |
| 129 | + return new Error(message); |
| 130 | +} |
| 131 | + |
| 132 | +function somethingThatThrows() { |
| 133 | + throw new createErrorIfRequired('failure'); |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +```js#evaluate:false |
| 138 | +expect(somethingThatThrows, 'to throw error', createErrorIfRequired); |
| 139 | +``` |
| 140 | + |
| 141 | +> Note: this is no longer supported by Unexpected v11 |
| 142 | +
|
| 143 | +The code above is intended to check the error type, but passing a |
| 144 | +function directly on the right-hand side would cause it to succeed. |
| 145 | +In version 11 it immediately leads to an error and the assertion |
| 146 | +must be written more explicitly allowing the issue to be caught: |
| 147 | + |
| 148 | +```js |
| 149 | +expect( |
| 150 | + somethingThatThrows, |
| 151 | + 'to throw error', |
| 152 | + expect.it('to equal', createErrorIfRequired('failure')) |
| 153 | +); |
| 154 | +``` |
| 155 | + |
| 156 | +When interacting with object types this affects: |
| 157 | + |
| 158 | +- [to satisfy](../assertions/any/to-satisfy/) |
| 159 | +- [to have keys satisfying](../assertions/object/to-have-keys-satisfying/) |
| 160 | +- [to have a value satisfying](../assertions/object/to-have-a-value-satisfying/) |
| 161 | +- [to have values satisfying](../assertions/object/to-have-values-satisfying/) |
| 162 | + |
| 163 | +```js |
| 164 | +function myCallback() {} |
| 165 | + |
| 166 | +const options = { |
| 167 | + data: null, |
| 168 | + callback: myCallback |
| 169 | +}; |
| 170 | + |
| 171 | +expect(options, 'to satisfy', { |
| 172 | + callback: myCallback |
| 173 | +}); |
| 174 | + |
| 175 | +expect(options, 'to have a value satisfying', myCallback); |
| 176 | +``` |
| 177 | + |
| 178 | +With array-like types it affects: |
| 179 | + |
| 180 | +- [to satisfy](../assertions/any/to-satisfy/) |
| 181 | +- [to have an item satisfying](../assertions/array-like/to-have-an-item-satisfying/) |
| 182 | +- [to have items satisfying](../assertions/array-like/to-have-items-satisfying/) |
| 183 | + |
| 184 | +```js |
| 185 | +const args = [myCallback]; |
| 186 | + |
| 187 | +expect(args, 'to have an item satisfying', myCallback); |
| 188 | +``` |
| 189 | + |
| 190 | +#### Support for `expect.async` has been removed |
| 191 | + |
| 192 | +`expect.async` was a helper for asynchronous tests. It predates Unexpected's |
| 193 | +promise support and we expect that it's very unlikely to be used by anyone. |
| 194 | + |
| 195 | +If you're using it, we recommend that you rewrite the given tests to a |
| 196 | +promise-driven flow as part of upgrading to Unexpected 11. |
| 197 | + |
| 198 | +#### `this.errorMode` etc. no longer available in assertion handlers |
| 199 | + |
| 200 | +This syntax has been deprecated since Unexpected 3: |
| 201 | + |
| 202 | +```js#evaluate:false |
| 203 | +expect.addAssertion('<string> to be foo', (expect, subject) => { |
| 204 | + this.errorMode = 'nested'; |
| 205 | + expect(subject, 'to equal', 'foo'); |
| 206 | +}); |
| 207 | +``` |
| 208 | + |
| 209 | +> Note: this is no longer supported by Unexpected v11 |
| 210 | +
|
| 211 | +To fix code like this, access the property on `expect` instead: |
| 212 | + |
| 213 | +```js |
| 214 | +expect.addAssertion('<string> to be foo', (expect, subject) => { |
| 215 | + expect.errorMode = 'nested'; |
| 216 | + expect(subject, 'to equal', 'foo'); |
| 217 | +}); |
| 218 | +``` |
0 commit comments