assert.notDeepStrictEqual()
Throws an AssertionError when two values are deeply, strictly equal.
assert.notDeepStrictEqual(actual, expected[, message])- Added in Node.js v1.2.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$actual | any | The value produced by the code under test. |
$expected | any | The value it must not deeply equal. |
$messageoptional | string | Error | Function | Error message, an Error instance to throw instead, or a function called on failure. |
Errors
Throws an AssertionError with code ERR_ASSERTION when the two values are deeply strictly equal; if message is an Error instance, that error is thrown instead.
Changelog
- PHP v26.0.0Message may now be a printf-like format string or function.
- PHP v9.0.0-0 and +0 are no longer considered equal, NaN is compared using SameValueZero, and Error names and messages are properly compared.
- PHP v8.0.0Set and Map contents are also compared.
- PHP v6.1.0Objects with circular references can be used as inputs.
assert.notDeepStrictEqual() is the negation of assert.deepStrictEqual(). It applies exactly the same recursive rules — prototypes, own enumerable keys, Object.is() on primitives, Map and Set contents — and throws when they all agree.
It ignores identity entirely
This is the trap. assert.notDeepStrictEqual(a, b) asks whether the two structures differ, not whether they are the same object. Two freshly built objects with the same fields will fail it. If what you want to prove is "a copy was made", the assertion you need is assert.notStrictEqual(), which compares references.
A weak assertion by nature
Passing means only "something, somewhere in this structure, is different". Any typo, any missing key, any accidental undefined return also satisfies it, so it rarely belongs on its own. The stronger version of most tests written with notDeepStrictEqual is a positive deepStrictEqual against the value you actually expect.
Where it does earn its place is in checking that an operation had some effect when the exact result is not interesting or not deterministic — a sort reordered a list, a sanitiser stripped something, a migration changed the shape of a record.
Example
When to use it
- Confirm a sanitiser actually removed a field rather than passing the payload through untouched
- Assert a sort or transform changed the order of a list
- Check that a schema migration produced a record with a different shape than the input
Frequently asked questions
Why does assert.notDeepStrictEqual() throw for two different objects?
Because it compares structure, not identity — two separately created objects with identical own enumerable properties and the same prototype are deeply equal, so the negative assertion fails. Use assert.notStrictEqual() to assert that two references differ.
What is the difference between notDeepStrictEqual and notStrictEqual?
notStrictEqual compares the two values with Object.is(), so any two distinct objects pass; notDeepStrictEqual walks both structures recursively and only passes when something inside them actually differs.
Does assert.notDeepStrictEqual() handle circular references?
Yes, since v6.1.0 objects containing circular references can be passed as inputs without causing infinite recursion. From v24.0.0 the recursion stops as soon as either side hits a cycle.
More examples
Checking a sanitiser actually stripped something
The notDeepStrictEqual line would still pass if stripSecrets removed only the password and leaked apiKey, which is why the deepStrictEqual beneath it is the assertion that actually protects you. Use the negative form as a cheap smoke check, never as the only one.
const assert = require('node:assert');
function stripSecrets(user) {
const { password, apiKey, ...safe } = user;
return safe;
}
const raw = { id: 1, name: 'ada', password: 'hunter2', apiKey: 'k-123' };
const safe = stripSecrets(raw);
// Weak: proves only that something changed.
assert.notDeepStrictEqual(safe, raw);
// Strong: proves exactly what came out.
assert.deepStrictEqual(safe, { id: 1, name: 'ada' });
// And the input was not mutated.
assert.deepStrictEqual(raw, { id: 1, name: 'ada', password: 'hunter2', apiKey: 'k-123' });
console.log('sanitiser verified');Pairing it with notStrictEqual for a deep clone test
The catch block is the point: a perfect deep clone is structurally equal to its source, so notDeepStrictEqual reports a failure for code that is working correctly. Reference-level questions belong to notStrictEqual; only after the clone is mutated does the structural assertion pass.
const assert = require('node:assert');
const source = { user: { id: 1, roles: ['admin'] } };
const clone = structuredClone(source);
// Different objects...
assert.notStrictEqual(clone, source);
assert.notStrictEqual(clone.user.roles, source.user.roles);
// ...but the same contents, so this THROWS:
try {
assert.notDeepStrictEqual(clone, source);
} catch (err) {
console.log('a correct clone fails notDeepStrictEqual:', err.code);
}
// Once the clone diverges, it passes.
clone.user.roles.push('dev');
assert.notDeepStrictEqual(clone, source);
console.log('after mutating the clone, the structures differ');Inside the built-in test runner
Three assertions describe the contract completely: the output differs from the input, it equals the expected normalised list, and the original array was not mutated in place. Only the middle one would catch a wrong sort order, so the negative assertion is documentation rather than protection.
const test = require('node:test');
const assert = require('node:assert/strict');
function normaliseTags(tags) {
return [...new Set(tags.map((t) => t.trim().toLowerCase()))].sort();
}
test('normaliseTags rewrites the input list', () => {
const input = [' Node ', 'node', 'ASSERT'];
const output = normaliseTags(input);
assert.notDeepStrictEqual(output, input); // it did something
assert.deepStrictEqual(output, ['assert', 'node']); // it did the right thing
assert.deepStrictEqual(input, [' Node ', 'node', 'ASSERT']); // input untouched
});
// $ node --test
// ✓ normaliseTags rewrites the input list (0.8ms)
Discussion