Search code examples
node.jsnode-test-runnernode-assert

Match a partial object with node:assert / assert arguments of mocked function


I have a mock and want to make sure that it was called with a specific set of arguments. However, I do not want to loop through all calls first.

it("should assert args", (t) => {
  const loggerSpy = t.mock.method(Logger, "info");
  myFunctionUnderTest();
  // this will obivously not work
  assert.deepStrictEqual(loggerInfoSpy.mock.calls, { arguments: [2, "No consumer application for given code", "MODEL_ENERGY_FLOW"] });
}) 

Is there a way to have an assertion like this? Basically I want to have something that would look in jest like this:

expect(
  loggerInfoSpy.mock.calls
).toEqual(
  expect.arrayContaining(
    [{ arguments: [2, "No consumer application for given code", "MODEL_ENERGY_FLOW"] }]
  )
);

Even better ofcourse would be an assertion similar to expect(loggerInfoSpy).toHaveBeenCalledWith(...).

Is there any way to add assertions / extend the node:assert module?


Solution

  • You'll have to create your own helper function to do this, you can attach it to the assert module yourself if you wish. Here's an example:

    const { describe, it, before } = require('node:test'),
          { isDeepStrictEqual } = require('node:util'),
          assert = require('node:assert');
    
    
    assert.calledWith = function(calls, chk) {
      if(calls.mock)
        calls = calls.mock.calls;
      else if(calls.calls)
        calls = calls.calls;
      for(let call of calls) {
        if(isDeepStrictEqual(call.arguments, chk))
          return true;
      }
      assert.fail(`Expected function to be called with ${JSON.stringify(chk)}`);
    }
    
    class Foo {
      bar(a,b,c) {
      }
    }
    
    describe('foo', function() {
      let foo;
      before(function(t) {
        foo = new Foo();
      });
      it('expect pass', (t) => {
        let spy = t.mock.method(foo,'bar');
        let args = [ 'asdfa', 24342, {x:'y'} ];
        foo.bar(...args);
        assert.calledWith(spy,args);
      });
      it('expect fail', (t) => {
        let spy = t.mock.method(foo,'bar');
        let args = [ 'asdfa', 24342, {x:'y'} ];
        foo.bar(...args);
        assert.calledWith(spy,args.reverse());
      });
    });
    
    ▶ foo
      ✔ expect pass (0.588027ms)
      ✖ expect fail (0.490436ms)
        AssertionError [ERR_ASSERTION]: Expected function to be called with [{"x":"y"},24342,"asdfa"]
            at assert.calledWith (/tmp/foo/test.js:15:10)
            at TestContext.<anonymous> (/tmp/foo/test.js:38:12)
            at Test.runInAsyncScope (node:async_hooks:206:9)
            at Test.run (node:internal/test_runner/test:631:25)
            at async Suite.processPendingSubtests (node:internal/test_runner/test:374:7) {
          generatedMessage: false,
          code: 'ERR_ASSERTION',
          actual: undefined,
          expected: undefined,
          operator: 'fail'
        }
    
    ▶ foo (2.386671ms)
    
    ℹ tests 2
    ℹ suites 1
    ℹ pass 1
    ℹ fail 1
    ℹ cancelled 0
    ℹ skipped 0
    ℹ todo 0
    ℹ duration_ms 8.105062
    
    ✖ failing tests:
    
    test at test.js:34:3
    ✖ expect fail (0.490436ms)
      AssertionError [ERR_ASSERTION]: Expected function to be called with [{"x":"y"},24342,"asdfa"]
          at assert.calledWith (/tmp/foo/test.js:15:10)
          at TestContext.<anonymous> (/tmp/foo/test.js:38:12)
          at Test.runInAsyncScope (node:async_hooks:206:9)
          at Test.run (node:internal/test_runner/test:631:25)
          at async Suite.processPendingSubtests (node:internal/test_runner/test:374:7) {
        generatedMessage: false,
        code: 'ERR_ASSERTION',
        actual: undefined,
        expected: undefined,
        operator: 'fail'
      }