Skip to content
Permalink
Browse files

assert: implement `assert.match()` and `assert.doesNotMatch()`

This adds a new functionality to the assertion module: a dedicated
check for regular expressions. So far it's possible to use
`assert.ok(regexp.test(string))`. This is not ideal though when it
comes to the error message, since it's not possible to know how
either of the input values look like. It's just known that the
assertion failed.
This allows to pass through the regular expression and the input
string. The string is then matched against the regular expression
and reports a expressive error message in case of a failure.

PR-URL: #30929
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
  • Loading branch information
BridgeAR committed Dec 12, 2019
1 parent b916035 commit d831dc1b77e29667688be4a26c34eb37fb45f50c
Showing with 220 additions and 4 deletions.
  1. +72 −0 doc/api/assert.md
  2. +48 −4 lib/assert.js
  3. +100 −0 test/parallel/test-assert.js
@@ -425,6 +425,42 @@ parameter is undefined, a default error message is assigned. If the `message`
parameter is an instance of an [`Error`][] then it will be thrown instead of the
`AssertionError`.

## `assert.doesNotMatch(string, regexp[, message])`
<!-- YAML
added: REPLACEME
-->

* `string` {string}
* `regexp` {RegExp}
* `message` {string|Error}

> Stability: 1 - Experimental
Expects the `string` input not to match the regular expression.

This feature is currently experimental and the name might change or it might be
completely removed again.

```js
const assert = require('assert').strict;
assert.doesNotMatch('I will fail', /fail/);
// AssertionError [ERR_ASSERTION]: The input was expected to not match the ...
assert.doesNotMatch(123, /pass/);
// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
assert.doesNotMatch('I will pass', /different/);
// OK
```

If the values do match, or if the `string` argument is of another type than
`string`, an [`AssertionError`][] is thrown with a `message` property set equal
to the value of the `message` parameter. If the `message` parameter is
undefined, a default error message is assigned. If the `message` parameter is an
instance of an [`Error`][] then it will be thrown instead of the
[`AssertionError`][].

## `assert.doesNotReject(asyncFn[, error][, message])`
<!-- YAML
added: v10.0.0
@@ -728,6 +764,42 @@ let err;
// at errorFrame
```

## `assert.match(string, regexp[, message])`
<!-- YAML
added: REPLACEME
-->

* `string` {string}
* `regexp` {RegExp}
* `message` {string|Error}

> Stability: 1 - Experimental
Expects the `string` input to match the regular expression.

This feature is currently experimental and the name might change or it might be
completely removed again.

```js
const assert = require('assert').strict;
assert.match('I will fail', /pass/);
// AssertionError [ERR_ASSERTION]: The input did not match the regular ...
assert.match(123, /pass/);
// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
assert.match('I will pass', /pass/);
// OK
```

If the values do not match, or if the `string` argument is of another type than
`string`, an [`AssertionError`][] is thrown with a `message` property set equal
to the value of the `message` parameter. If the `message` parameter is
undefined, a default error message is assigned. If the `message` parameter is an
instance of an [`Error`][] then it will be thrown instead of the
[`AssertionError`][].

## `assert.notDeepEqual(actual, expected[, message])`
<!-- YAML
added: v0.1.21
@@ -25,6 +25,7 @@ const {
ObjectIs,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
RegExpPrototypeTest,
} = primordials;

const { Buffer } = require('buffer');
@@ -532,7 +533,7 @@ class Comparison {
if (actual !== undefined &&
typeof actual[key] === 'string' &&
isRegExp(obj[key]) &&
obj[key].test(actual[key])) {
RegExpPrototypeTest(obj[key], actual[key])) {
this[key] = actual[key];
} else {
this[key] = obj[key];
@@ -578,7 +579,7 @@ function expectedException(actual, expected, message, fn) {
// Handle regular expressions.
if (isRegExp(expected)) {
const str = String(actual);
if (expected.test(str))
if (RegExpPrototypeTest(expected, str))
return;

if (!message) {
@@ -613,7 +614,7 @@ function expectedException(actual, expected, message, fn) {
for (const key of keys) {
if (typeof actual[key] === 'string' &&
isRegExp(expected[key]) &&
expected[key].test(actual[key])) {
RegExpPrototypeTest(expected[key], actual[key])) {
continue;
}
compareExceptionKey(actual, expected, key, message, keys, fn);
@@ -779,7 +780,7 @@ function hasMatchingError(actual, expected) {
if (typeof expected !== 'function') {
if (isRegExp(expected)) {
const str = String(actual);
return expected.test(str);
return RegExpPrototypeTest(expected, str);
}
throw new ERR_INVALID_ARG_TYPE(
'expected', ['Function', 'RegExp'], expected
@@ -884,6 +885,49 @@ assert.ifError = function ifError(err) {
}
};

function internalMatch(string, regexp, message, fn) {
if (!isRegExp(regexp)) {
throw new ERR_INVALID_ARG_TYPE(
'regexp', 'RegExp', regexp
);
}
const match = fn.name === 'match';
if (typeof string !== 'string' ||
RegExpPrototypeTest(regexp, string) !== match) {
if (message instanceof Error) {
throw message;
}

const generatedMessage = !message;

// 'The input was expected to not match the regular expression ' +
message = message || (typeof string !== 'string' ?
'The "string" argument must be of type string. Received type ' +
`${typeof string} (${inspect(string)})` :
(match ?
'The input did not match the regular expression ' :
'The input was expected to not match the regular expression ') +
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
const err = new AssertionError({
actual: string,
expected: regexp,
message,
operator: fn.name,
stackStartFn: fn
});
err.generatedMessage = generatedMessage;
throw err;
}
}

assert.match = function match(string, regexp, message) {
internalMatch(string, regexp, message, match);
};

assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
internalMatch(string, regexp, message, doesNotMatch);
};

// Expose a strict only variant of assert
function strict(...args) {
innerOk(strict, args.length, ...args);
@@ -1361,3 +1361,103 @@ assert.throws(
'prototype.\n\nError message:\n\nfoobar'
}
);

// Multiple assert.match() tests.
{
assert.throws(
() => assert.match(/abc/, 'string'),
{
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "regexp" argument must be an instance of RegExp. ' +
"Received type string ('string')"
}
);
assert.throws(
() => assert.match('string', /abc/),
{
actual: 'string',
expected: /abc/,
operator: 'match',
message: 'The input did not match the regular expression /abc/. ' +
"Input:\n\n'string'\n",
generatedMessage: true
}
);
assert.throws(
() => assert.match('string', /abc/, 'foobar'),
{
actual: 'string',
expected: /abc/,
operator: 'match',
message: 'foobar',
generatedMessage: false
}
);
const errorMessage = new RangeError('foobar');
assert.throws(
() => assert.match('string', /abc/, errorMessage),
errorMessage
);
assert.throws(
() => assert.match({ abc: 123 }, /abc/),
{
actual: { abc: 123 },
expected: /abc/,
operator: 'match',
message: 'The "string" argument must be of type string. ' +
'Received type object ({ abc: 123 })',
generatedMessage: true
}
);
assert.match('I will pass', /pass$/);
}

// Multiple assert.doesNotMatch() tests.
{
assert.throws(
() => assert.doesNotMatch(/abc/, 'string'),
{
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "regexp" argument must be an instance of RegExp. ' +
"Received type string ('string')"
}
);
assert.throws(
() => assert.doesNotMatch('string', /string/),
{
actual: 'string',
expected: /string/,
operator: 'doesNotMatch',
message: 'The input was expected to not match the regular expression ' +
"/string/. Input:\n\n'string'\n",
generatedMessage: true
}
);
assert.throws(
() => assert.doesNotMatch('string', /string/, 'foobar'),
{
actual: 'string',
expected: /string/,
operator: 'doesNotMatch',
message: 'foobar',
generatedMessage: false
}
);
const errorMessage = new RangeError('foobar');
assert.throws(
() => assert.doesNotMatch('string', /string/, errorMessage),
errorMessage
);
assert.throws(
() => assert.doesNotMatch({ abc: 123 }, /abc/),
{
actual: { abc: 123 },
expected: /abc/,
operator: 'doesNotMatch',
message: 'The "string" argument must be of type string. ' +
'Received type object ({ abc: 123 })',
generatedMessage: true
}
);
assert.doesNotMatch('I will pass', /different$/);
}

0 comments on commit d831dc1

Please sign in to comment.
You can’t perform that action at this time.