Closed Krinkle closed 1 month ago
I support including this, and there's even a use case in jQuery for asserting that early progress of an element animation approximates the pre-animation state (rather than e.g. introducing a discontinuity based on unit mismatch) at https://github.com/jquery/jquery/blob/99151d7ab0923aa3aeeb1b957a9063e4e20d31ae/test/unit/effects.js#L1830-L1832 .
Peer review of implementations:
qunit-assert-close:
Boils down to Math.abs(actual - expected) <= delta
https://github.com/JamesMGreene/qunit-assert-close/blob/v2.1.2/qunit-assert-close.js
Chai:
Idem, Math.abs(actual - expected) <= delta
https://github.com/chaijs/chai/blob/v5.1.1/lib/chai/core/assertions.js#L3010
Deno
Similar, albeit swapped, reducing to Math.abs(expected - actual) <= tolerance
https://github.com/denoland/deno_std/blob/0.224.0/assert/assert_almost_equals.ts#L21
Expect.js
Different approach as "within(min, max)"
using result = actual >= min && actual <= max
https://github.com/Automattic/expect.js/blob/0.3.1/index.js#L246
Jasmine (and therefore Jest) Different approach as "toBeCloseTo(,,precision=2)" where precision is the number of digits, not the delta. The main logic here is:
const pow = Math.pow(10, precision + 1);
const delta = Math.abs(expected - actual);
const maxDelta = Math.pow(10, -precision) / 2;
result = Math.round(delta * pow) <= maxDelta * pow
https://github.com/jasmine/jasmine/blob/v5.1.2/src/core/matchers/toBeCloseTo.js#L38
I'm going to go with @JamesMGreene 's qunit-assert-close for the most part, with an added type check in case the delta
argumet is missing, to catch otherwise-subtle mistakes.
Just wanted to swing by to say thanks for the note, Timo! ❤
I added a README notice in https://github.com/JamesMGreene/qunit-assert-close/commit/e0300bae14cccdb7ab8ca44d68928e18fb590aea and archived the repo. 🗄 🧹
What are you trying to do?
Assert that X is equal to Y within a certain tolerance, eg allow a delta of 0.1 above or below it. It most cases, I believe what people actually mean is to round the number to a certain decimal place before comparing it (except with deltas like 1.0 or higher
Prior art
Plugin: https://github.com/JamesMGreene/qunit-assert-close, https://www.npmjs.com/package/qunit-assert-close
This has been unmaintained for a while and currently has some compat issues with QUnit 2 Assertion API (ie context-based instead of global state). This is for example forked and worked around in https://github.com/jquery/jquery-ui/commit/546214e86956804a1b02da173a4c6c5ddea11454.
Other JS test frameworks:
Other languages:
Proposal
In keeping with my philosophy on assertions, I find myself biased towards not supporting these kind of loose assertions. However, there's no denying that this kind of method is commonly built-in for most test frameworks. That doesn't mean we need to copy it, but it does mean we need to at least have an easily found example and answer for how we approach this situation.
Unlike other loose assertions, this is a case where existing options for partial assertions (such as propEqual and propContains) don't help. And, ad-hoc expressions passed to
assert.true
seem unreasonably non-trivial, because historically the language hasn't come with an easy way to round a float to a given set of decimal places. Besides, such method would bring unpleasant semantics into it given what a float is. From what I can see, this remains true as of ES2022 (Number toPrecision, toFixed, Math.trunc, Math.round, all come close though).The approaches I see are to either provide a utility method that "does what I mean" and would be used in conjunction with assert.equal, like
assert.equal(q.round(x, 0.1), 2.4))
, or to add a new assertion method for this purpose, and thus allow it to be used for the wider set of use cases that include more questionable deltas like fractions larger than 1.0 such as 15.6.Python's approach based on decimal places is interesting as well. It seems to encourage strictness and not support a false sense of security through arbitrary and needlessly precise deltas.
On the other hand, there is also an argument to be made for easing switching costs, and meeting surface-level expectations, allowing devs to "choose your own adventure". Especially given that we have ESLint nowadays, where the "recommended" preset could discourage use of this method, or limit its use to only passing
1
as the significant digit of thedelta
parameter (eg 0.1, 0.01, 0.001, etc).