Skip to content

Commit dc70085

Browse files
doc: clarify behavior of destructured assertion methods in Assert instance
1 parent a62506e commit dc70085

File tree

3 files changed

+151
-3
lines changed

3 files changed

+151
-3
lines changed

doc/api/assert.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,20 +242,25 @@ assertInstance.deepStrictEqual({ a: 1 }, { a: 2 });
242242
// Shows a full diff in the error message.
243243
```
244244

245-
**Important**: When destructuring assertion methods from an `Assert` instance, the methods lose their connection to the instance's configuration options (such as `diff` and `strict` settings). The destructured methods will behave with default options instead.
245+
**Important**: When destructuring assertion methods from an `Assert` instance,
246+
the methods lose their connection to the instance's configuration options (such as `diff` and `strict` settings).
247+
The destructured methods will fall back to default behavior instead.
246248

247249
```js
248250
const myAssert = new Assert({ diff: 'full' });
249251

250252
// This works as expected - uses 'full' diff
251253
myAssert.strictEqual({ a: 1 }, { b: { c: 1 } });
252254

253-
// This loses the 'full' diff setting - uses default 'simple' diff
255+
// This loses the 'full' diff setting - falls back to default 'simple' diff
254256
const { strictEqual } = myAssert;
255257
strictEqual({ a: 1 }, { b: { c: 1 } });
256258
```
257259

258-
To maintain custom options when using destructured methods, pass the options to individual assertion calls or avoid destructuring.
260+
When destructured, methods lose access to the instance's `this` context and revert to default assertion behavior
261+
(diff: 'simple', non-strict mode).
262+
To maintain custom options when using destructured methods, avoid
263+
destructuring and call methods directly on the instance.
259264

260265
## `assert(value[, message])`
261266

lib/assert.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ function Assert(options) {
135135
// both the actual and expected values to the assertion error for
136136
// display purposes.
137137

138+
// DESTRUCTURING WARNING: All Assert.prototype methods use optional chaining
139+
// (this?.[kOptions]) to safely access instance configuration. When methods are
140+
// destructured from an Assert instance (e.g., const {strictEqual} = myAssert),
141+
// they lose their `this` context and will use default behavior instead of the
142+
// instance's custom options.
143+
138144
function innerFail(obj) {
139145
if (obj.message instanceof Error) throw obj.message;
140146

@@ -154,6 +160,10 @@ Assert.prototype.fail = function fail(message) {
154160
internalMessage = true;
155161
}
156162

163+
// IMPORTANT: When adding new references to `this`, ensure they use optional chaining
164+
// (this?.[kOptions]?.diff) to handle cases where the method is destructured from an
165+
// Assert instance and loses its context. Destructured methods will fall back
166+
// to default behavior when `this` is undefined.
157167
const errArgs = {
158168
operator: 'fail',
159169
stackStartFn: fail,
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const { Assert } = require('assert');
6+
const { test } = require('node:test');
7+
8+
// Disable colored output to prevent color codes from breaking assertion
9+
// message comparisons. This should only be an issue when process.stdout
10+
// is a TTY.
11+
if (process.stdout.isTTY) {
12+
process.env.NODE_DISABLE_COLORS = '1';
13+
}
14+
15+
test('Assert class destructuring behavior - diff option', () => {
16+
const assertInstanceFull = new Assert({ diff: 'full' });
17+
const assertInstanceSimple = new Assert({ diff: 'simple' });
18+
19+
assertInstanceFull.throws(
20+
() => assertInstanceFull.strictEqual({ a: 1 }, { a: 2 }),
21+
(err) => {
22+
assert.strictEqual(err.diff, 'full');
23+
return true;
24+
}
25+
);
26+
27+
assertInstanceSimple.throws(
28+
() => assertInstanceSimple.strictEqual({ a: 1 }, { a: 2 }),
29+
(err) => {
30+
assert.strictEqual(err.diff, 'simple');
31+
return true;
32+
}
33+
);
34+
35+
const { strictEqual: strictEqualSimple } = assertInstanceSimple;
36+
const { strictEqual: strictEqualFull } = assertInstanceFull;
37+
const { deepStrictEqual: deepStrictEqualFull } = assertInstanceFull;
38+
const { equal: equalFull } = assertInstanceFull;
39+
40+
assert.throws(
41+
() => strictEqualSimple({ a: 1 }, { a: 2 }),
42+
(err) => {
43+
assert.strictEqual(err.diff, 'simple');
44+
return true;
45+
}
46+
);
47+
48+
assert.throws(
49+
() => strictEqualFull({ a: 1 }, { a: 2 }),
50+
(err) => {
51+
assert.strictEqual(err.diff, 'simple');
52+
return true;
53+
}
54+
);
55+
56+
assert.throws(
57+
() => deepStrictEqualFull({ a: 1 }, { a: 2 }),
58+
(err) => {
59+
assert.strictEqual(err.diff, 'simple');
60+
return true;
61+
}
62+
);
63+
64+
assert.throws(
65+
() => equalFull({ a: 1 }, { a: 2 }),
66+
(err) => {
67+
assert.strictEqual(err.diff, 'simple');
68+
return true;
69+
}
70+
);
71+
});
72+
73+
test('Assert class destructuring behavior - strict option', () => {
74+
const assertInstanceNonStrict = new Assert({ strict: false });
75+
const assertInstanceStrict = new Assert({ strict: true });
76+
77+
assertInstanceNonStrict.equal(2, '2');
78+
79+
assert.throws(
80+
() => assertInstanceStrict.equal(2, '2'),
81+
assert.AssertionError
82+
);
83+
84+
const { equal: equalNonStrict } = assertInstanceNonStrict;
85+
const { equal: equalStrict } = assertInstanceStrict;
86+
87+
equalNonStrict(2, '2');
88+
assert.throws(
89+
() => equalStrict(2, '2'),
90+
assert.AssertionError
91+
);
92+
});
93+
94+
test('Assert class destructuring behavior - comprehensive methods', () => {
95+
const myAssert = new Assert({ diff: 'full', strict: false });
96+
97+
const {
98+
fail,
99+
equal,
100+
strictEqual,
101+
deepStrictEqual,
102+
throws,
103+
match,
104+
doesNotMatch
105+
} = myAssert;
106+
107+
assert.throws(() => fail('test message'), (err) => {
108+
assert.strictEqual(err.diff, 'simple');
109+
assert.strictEqual(err.message, 'test message');
110+
return true;
111+
});
112+
113+
assert.throws(() => equal({ a: 1 }, { a: 2 }), (err) => {
114+
assert.strictEqual(err.diff, 'simple');
115+
return true;
116+
});
117+
118+
assert.throws(() => strictEqual({ a: 1 }, { a: 2 }), (err) => {
119+
assert.strictEqual(err.diff, 'simple');
120+
return true;
121+
});
122+
123+
assert.throws(() => deepStrictEqual({ a: 1 }, { a: 2 }), (err) => {
124+
assert.strictEqual(err.diff, 'simple');
125+
return true;
126+
});
127+
128+
throws(() => { throw new Error('test'); }, Error);
129+
130+
match('hello world', /world/);
131+
132+
doesNotMatch('hello world', /xyz/);
133+
});

0 commit comments

Comments
 (0)