assertEquals conflicts with the JavaScript's meaning of "equality"
Describe the bug
assertEquals incorrectly treats NaN which as equal to itself.
For reference, JavaScript has 3 similar operations for equivalence:
NaN == NaN # false"loose equality"NaN === NaN # false"strict equality"Object.is(NaN, NaN) # true"same value"
Steps to Reproduce
import * as asserts from 'https://deno.land/std/assert/mod.ts'
asserts.assertEquals(NaN, NaN)
asserts.assertStrictEquals(NaN, NaN)
asserts.assertAlmostEquals(NaN, NaN)
Expected behavior
As written, all three of the above assertions should fail. If the current NaN behavior is intended, it should be documented and/or renamed to something like assertAlike or assertSameValue which doesn't conflict with Javascript's definitions of "equal".
Environment
- OS: MacOS
- deno version: 1.35.3
- std version: 0.196.0
In Node assert.equal and assert.strictEqual don't throw with the below:
assert.equal(NaN, NaN)
assert.strictEqual(NaN, NaN)
Also in Jest, the below doesn't throw:
expect(NaN).toEqual(NaN);
expect(NaN).toBe(NaN);
I think assertion libs treating NaN equals itself is standard in JS ecosystem
@kt3k good call in comparing to other frameworks. It's a bit of a rabbit hole.
Here's what I found:
Node
assert.equal and assert.strictEqual both treat NaN as equal to itself.
assert.strictEqualusesObject.is.assert.equaluses the==operator, with a specialNaNhandling to matchassert.strictEqual.- The equality operators for objects are by-reference (the recursive value semantics are under a function
deepStrictEqual)
Jest
The documentation is rather sparse, but both toEqual and toStrictEqual treat NaN as equal to itself.
toEqualchecks value equality https://jestjs.io/docs/expect#toequalvaluetoStrictEqualchecks value equality, but stricter https://jestjs.io/docs/expect#tostrictequalvalue- The equality operators for objects are by-value.
toBechecks reference equality.
Chai
The equal method treats NaN as NOT equal to itself. the eql treats NaN as equal to itself.
equalcompares with===eqluses a deep equality operator which usesObject.isfor primitive comparisons.
My gut feeling is that if someone cares about NaN semantics in JS, they could just do
assert(maybeNaN() === anotherMaybeNaN());
Most developers do not know or care about Object.is vs ===. assertEqual already does everything by value, so I think this is consistent within this model.
I feel like this is more of an issue of documentation than of behavior. We should make sure that developers that DO care know that assertEquals does equality by value in case of NaN.
Most developers do not know or care about
Object.isvs===.assertEqualalready does everything by value, so I think this is consistent within this model.
Unfortunately, I don't think that's the case. NaN is a pretty common object to compare especially where you might want assertions.
I do think Object.is is the most sane comparison that most people will need most of the time. And that the non-reflexive semantics of IEEE 754's compareQuietEqual do not deserved to be called "equal" nor reflected in a language's most handy equality operator.
I feel like this is more of an issue of documentation than of behavior. We should make sure that developers that DO care know that
assertEqualsdoes equality by value in case of NaN.
It's documentation AND naming. I think JavaScript as a language is at fault here. It screwed up with "equality" twice, and now the word is broken.
For some reason, assertEqual doesn't even use "same value" for numbers, which is IMO the wrong choice.
I'd maybe rename:
assertStrictEquals->assertIsassertEquals->assertAlike.
Closing due to the inactivity.