Skip to content

Asserting a range with descriptive error messages  #4461

Closed

Description

@nicholaswmin

Node.js Version

v22

NPM Version

10.8.2

Operating System

MacOS Sonoma

Subsystem

assert

Description

I work a lot with performance testing code which frequently leads in the unfortunate position where I need to assert ranges rather than exact values, i.e

is value between 1500 and 2000?

Now, to do that I use a pair of assert.ok like so:

const value = 1700

assert.ok(value >= 1500)
assert.ok(value <= 2000)

so far so good, could be better but that's clean and concise enough for my taste.

The problem is that setting a descriptive error message quickly becomes rather repetitive and convoluted.

I have to resort to something akin to this:

assert.ok(value >= 1500, `expected value >= 1500 , got: ${value}`)
assert.ok(value <= 2000, `expected value <= 2000 , got: ${value}`)

Apart from cooking up my own assertion functions, is there a better way to do this out-the-box that I'm missing?

Activity

  1. avivkeller commented on Aug 4, 2024

    @avivkeller
    Member

    CC @nodejs/assert

  2. moved this from Awaiting Triage to Triaged in Node.js feature requestson Sep 4, 2024
  3. gireeshpunathil commented on Sep 4, 2024

    @gireeshpunathil
    Member

    looks reasonable to me!

  4. gireeshpunathil commented on Sep 4, 2024

    @gireeshpunathil
    Member

    @nicholaswmin - pls feel free to raise a PR if you are willing to!

  5. avivkeller commented on Sep 4, 2024

    @avivkeller
    Member

    Although, doesn't the assert message include the failed assertion line, so wouldn't it already show the failure?

  6. nicholaswmin commented on Sep 4, 2024

    @nicholaswmin
    Author

    @redyetidev here's the output:

    import test from 'node:test'
    
    await test('is within range',  async t => {
      const value = 2700
      
      t.assert.ok(value >= 1500)
      t.assert.ok(value <= 2000)
    })
    ✖ is within range (1.220792ms)
      AssertionError [ERR_ASSERTION]: false == true
          at assert.<computed> [as ok] (node:internal/test_runner/test:261:18)
          at TestContext.<anonymous> (file:///Users/nicholaswmin/Projects/fsm/scratch.js:7:12)
          at Test.runInAsyncScope (node:async_hooks:211:14)
          at Test.run (node:internal/test_runner/test:878:25)
          at Test.processPendingSubtests (node:internal/test_runner/test:590:18)
          at node:internal/test_runner/harness:252:20
          at node:internal/process/task_queues:151:7
          at AsyncResource.runInAsyncScope (node:async_hooks:211:14)
          at AsyncResource.runMicrotask (node:internal/process/task_queues:148:8) {
        generatedMessage: true,
        code: 'ERR_ASSERTION',
        actual: false,
        expected: true,
        operator: '=='
      }
    

    My "complaint" is that it doesn't print value being asserted against, unless I specifically code it to so with that string interpolation.

    Now granted, the above is impossible to do with the code I'm providing because that's a Boolean expression.
    An ideal API might be something like:

      t.assert.ok(value <= 2000, value)
      t.assert.ok(value <= 2000, value)

    AssertionError [ERR_ASSERTION]: Failed, value: 2700 was not ok

    Hmm.. I'm a bit confused... Not sure why I opened this issue as what I'm proposing sounds like a silly API.

    Doing this:

    t.assert.ok(value <= 2000, value)

    prints the value anyway.

    I don't remember what I had in mind. Sorry.

  7. avivkeller commented on Sep 4, 2024

    @avivkeller
    Member

    assert.isGreaterThan (and vise versa) could work if you'd like to add it?

  8. nicholaswmin commented on Sep 4, 2024

    @nicholaswmin
    Author

    I'm personally against adding specific assertion checks to assert like that.

    It'll eventually snowball into a myriad utilities. I miss the hyper-declarative syntax of Chai but I've also come to love the minimal, no-frills API surface of assert since I don't need to memorise a million method names.

    ... I don't remember what i had in mind when i opened this.

    If I need range checks I can extend assert locally.

    I think this needs to be closed ??

  9. nicholaswmin commented on Sep 4, 2024

    @nicholaswmin
    Author

    The closest thing I can figure out that would help without adding specific assertion checks is printing the expression itself on assert.ok().

    An example:

    import test from 'node:test'
    
    await test('foo', async t => {
      const value = 2700
      
      t.assert.ok(value >= 1500)
      t.assert.ok(value <= 2000)
    })

    logs:

    ✖ is in range (1.220875ms)
      AssertionError [ERR_ASSERTION]: `value >= 1500`
          at assert.<computed> [as ok] (node:internal/test_runner/test:261:18)
          at TestContext.<anonymous> (file:///Users/nicholaswmin/Projects/fsm/scratch.js:7:12)
          at Test.runInAsyncScope (node:async_hooks:211:14)
          at Test.run (node:internal/test_runner/test:878:25)
          at Test.processPendingSubtests (node:internal/test_runner/test:590:18)
          at node:internal/test_runner/harness:252:20
          at node:internal/process/task_queues:151:7
          at AsyncResource.runInAsyncScope (node:async_hooks:211:14)
          at AsyncResource.runMicrotask (node:internal/process/task_queues:148:8) {
        generatedMessage: true,
        code: 'ERR_ASSERTION',
        actual: false,
        expected: true,
        operator: '=='
      }
    
    

    which is more descriptive than the current:

    AssertionError [ERR_ASSERTION]: false == true
    

    Not sure if that's doable without doing esprima-like parsing though?

  10. avivkeller commented on Sep 4, 2024

    @avivkeller
    Member

    For your specific case, a custom error (E.G. throw new assert.AssertionError({actual: 1, expected:2, operator:">="})) can be thrown.

    Not sure if that's doable without doing esprima-like parsing though?

    In some cases, that is what happens:

    import assert from 'node:assert';
    
    assert.ok(25 > 50);
    node:internal/modules/run_main:123
        triggerUncaughtException(
        ^
    
    AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
    
      assert.ok(25 > 50)
    
        at file:///Users/avivkeller/Documents/tmp/repro.mjs:3:8
        at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
        at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:482:26)
        at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5) {
      generatedMessage: true,
      code: 'ERR_ASSERTION',
      actual: false,
      expected: true,
      operator: '=='
    }
    
    Node.js v22.8.0
    
  11. nicholaswmin commented on Sep 4, 2024

    @nicholaswmin
    Author

    In some cases, that is what happens:

    Yes. That works! That's more than enough.

    I can repro your working example with assert but not with t.assert

  12. avivkeller commented on Sep 4, 2024

    @avivkeller
    Member

    I'm aware. I'm opening an issue for it as we speak.

  13. 4 remaining items

  14. avivkeller commented on Sep 4, 2024

    @avivkeller
    Member
  15. nicholaswmin commented on Sep 4, 2024

    @nicholaswmin
    Author

    Amazing;

    sorry, life got in the way ...

  16. gireeshpunathil commented on Nov 13, 2024

    @gireeshpunathil
    Member

    @nicholaswmin - is your proposal addressed? can this be closed now as resolved?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions