assert.notDeepStrictEqual()

Throws an AssertionError when two values are deeply, strictly equal.

Signature
assert.notDeepStrictEqual(actual, expected[, message])
  • Added in Node.js v1.2.0

Parameters

ParameterTypeDescription
$actualanyThe value produced by the code under test.
$expectedanyThe value it must not deeply equal.
$messageoptionalstring | Error | FunctionError 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · javascript
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.

Example · javascript
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.

Example · javascript
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

  • Be the first to comment on this lesson.