assert-match
v1.1.1
Published
assert + matchers <3
Downloads
17,650
Maintainers
Readme
assert-match
assert-match
is the enhancement of the standard assert
module with matchers.
Short example
import assert from 'assert-match'
import { loose, arrayOf, type } from 'assert-match/matchers'
// or const { loose, arrayOf, type } = assert.matchers
const actual = {
str: 'abc',
obj: { b: 1, c: 2 },
nums: [ 1, 2, 'x' ],
},
expected = {
str: 'abc',
obj: loose({ b: 1 }),
nums: arrayOf(type('number')),
}
assert.deepEqual(actual, expected)
// AssertionError: { str: 'abc', obj: { b: 1 }, nums: [ 1, 2, 'x' ] } deepEqual
// { str: 'abc', obj: { b: 1 }, nums: [ 1, 2, { '[typeof]': 'number' } ] }
// + expected - actual
//
// {
// "nums": [
// 1
// 2
// - "x"
// + {
// + "[typeof]": "number"
// + }
// ]
Installation
npm install assert-match
Usage
Use assert-match
in all the same places where you would use built-in assert
:
const assert = require('assert-match')
// ...
assert.deepEqual(actual, expected)
Assertions
assert-match
enhances standard assert
's deep
-family assertions:
assert.deepEqual (actual, expected, [message])
assert.deepStrictEqual (actual, expected, [message])
assert.notDeepEqual (actual, expected, [message])
assert.notDeepStrictEqual (actual, expected, [message])
assert-match
allows you to check actual value (or its property) not
against a specific expected value as standard deep
assertions do, but
against a matcher or a combination of them.
Without using matchers these assertions behave exactly like their standard
counterparts which has been tested against the
same set of tests as the standard ones.
Other assertions of assert
are also exported by assert-match
but not
enhanced with matchers support.
Matchers
A matcher is an objects used to check if a value satisfies some requirements defined by the matcher.
Matchers can be placed on the top level of expected value or on some of its properties.
An awesome point about matchers is the ability to combine them! It gives you a way to create powerful matching structures using small set of matchers.
Matchers and combinations of them can be reused and recombined across multiple assertions.
In cases of assertion errors matchers participate in providing your test runner with error details.
assert-match
defines the following matchers:
- strict (expected)
- loose (expected)
- any (expected)
- not (expected)
- every (expected)
- some (expected)
- arrayOf (expected)
- contains (expected)
- type (expected)
- primitive (expected)
- regex (expected)
- gt (expected)
- gte (expected)
- lt (expected)
- lte (expected)
- custom (expectedFn)
In all of the following matchers descriptions actual refers to actual value or its property, corresponding to the matcher in expected, both passed to an assertion.
strict (expected)
Returns an instance of the root matchers class. All other matchers inherit from
that class. It checks whether two values are equal in depth. Actual comparison
operator (== or ===) for primitives depends on assertion in which this matcher
is used (for example, == is used for deepEqual
whereas === is used for
deepStrictEqual
). If expected contains a matcher somewhere on it, then
check for corresponding actual value is passed to that matcher.
If applied to another matcher it produces equivalent one, meaning that for
example strict(aMatcher(expected))
returns matcher equivalent to
aMatcher(expected)
. Actually, deepEqual
and deepStrictEqual
assertions
wrap its expected argument in strict
matcher implicitly.
assert.deepEqual({ a: 1, b: 2 }, strict({ a: 1, b: 2 })) // passes
assert.deepEqual({ a: 1, b: 2 }, strict({ a: 1 })) // throws
assert.deepEqual({ a: 1 }, strict({ a: 1, b: 2 })) // throws
loose (expected)
Similar to strict
matcher but requires only subset of actual properties to
be equal in depth to those of expected.
assert.deepEqual({ a: 1, b: 2 }, loose({ a: 1, b: 2 })) // passes
assert.deepEqual({ a: 1, b: 2 }, loose({ a: 1 })) // passes
assert.deepEqual({ a: 1 }, loose({ a: 1, b: 2 })) // throws
any (expected)
Matches anything. Can be used if value or existence of a specific actual
property does not matter. It is supposed to be used in context of strict
matcher, in context of loose
matcher it makes a little sense.
assert.deepEqual(undefined, any()) // passes
assert.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: any() }) // passes
assert.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 5, c: any() }) // throws
not (expected)
It implicitly wraps expected in strict
matcher, matches actual value
against it and inverts result. notDeepEqual
and notDeepStrictEqual
assertions wrap its expected argument in not
matcher implicitly.
assert.deepEqual({ a: 1, b: 2 }, not({ a: 1, b: 2 })) // throws
assert.deepEqual({ a: 1, b: 2 }, not({ a: 1 })) // passes
assert.deepEqual({ a: 1 }, not({ a: 1, b: 2 })) // passes
every (expected)
expected should be an array. If it is not, than it is treated as one-element
array. Each element of expected array is wrapped implicitly in strict
matcher. every
matcher checks whether actual value matches all matchers of
expected.
assert.deepEqual({ a: 1, b: 2 }, every([ loose({ a: 1 }), loose({ b: 2 }) ])) // passes
assert.deepEqual({ a: 1, b: 2 }, every([ loose({ a: 1 }), loose({ c: 3 }) ])) // throws
assert.deepEqual({ a: 1, b: 2 }, every([ { c: 3 } ])) // throws
assert.deepEqual({ a: 1, b: 2 }, every(loose({ a: 1 }))) // passes
some (expected)
expected should be an array. If it is not, than it is treated as one-element
array. Each element of expected array is wrapped implicitly in strict
matcher. some
matcher checks whether actual value matches at least one
matcher of expected.
assert.deepEqual({ a: 1, b: 2 }, some([ loose({ a: 1 }), loose({ b: 2 }) ])) // passes
assert.deepEqual({ a: 1, b: 2 }, some([ loose({ a: 1 }), loose({ c: 3 }) ])) // passes
assert.deepEqual({ a: 1, b: 2 }, some([ { c: 3 } ])) // throws
assert.deepEqual({ a: 1, b: 2 }, some(loose({ a: 1 }))) // passes
arrayOf (expected)
Expects actual value to be a non-empty array, check fails if it is not.
Implicitly wraps expected in strict
matcher. Checks that all elements of
the array match expected.
assert.deepEqual([ 1, 1, 1 ], arrayOf(1)) // passes
assert.deepEqual([ 1, 1, 'a' ], arrayOf(1)) // throws
assert.deepEqual(1, arrayOf(1)) // throws
contains (expected1 [, expected2, ...])
Expects actual value to be a non-empty array, check fails if it is not. Accepts a list of expected. Checks that each element of the expected list matches at least one element in the actual array.
assert.deepEqual([ 1, 1, 1 ], contains(1)) // passes
assert.deepEqual([ 1, 'a', 'a' ], contains(1)) // passes
assert.deepEqual([ 'a', 'a', 'a' ], contains(1)) // throws
assert.deepEqual(1, contains(1)) // throws
assert.deepEqual([ 1, 2, 3 ], contains(1, 2)) // passes
assert.deepEqual([ 1, 2, 3 ], contains(1, 10)) // throws
type (expected)
if expected is a string than actual is checked to be a primitive of that type. If expected is a constructor than actual is checked to be an instance of that type.
assert.deepEqual(5, type('number')) // passes
assert.deepEqual([ 1, 2, 3 ], type(Array)) // passes
assert.deepEqual(5, type('string')) // throws
assert.deepEqual({ a: 1 }, type({ a: 1 })) // throws
primitive (expected)
If expected is a matcher than actual is converted to primitive and matched against expected. Otherwise, actual and expected both converted to primitive and compared (actual operator == or === depends on assertion used).
assert.deepEqual({}, primitive('[object Object]')) // passes
assert.deepEqual(new String('abc'), primitive('abc')) // passes
assert.deepEqual({ toString: () => 'abc' }, primitive('abc')) // passes
assert.deepEqual(1, primitive(1)) // passes
assert.deepEqual(10, primitive(1)) // throws
assert.deepEqual(1, primitive('1')) // passes
assert.deepStrictEqual(1, primitive('1')) // throws
assert.deepEqual({}, primitive(regex('obj'))) // passes
assert.deepEqual({}, primitive(regex('abc'))) // throws
regex (expected)
expected is converted to a RegExp and actual is tested against it.
assert.deepEqual('abc', regex('^a')) // passes
assert.deepEqual('[object Object]', regex({})) // passes
assert.deepEqual('123', regex(/^\d+$/)) // passes
assert.deepEqual('123', regex('^\D+$')) // throws
gt (expected)
Checks if actual is greater than expected.
assert.deepEqual('b', gt('a')) // passes
assert.deepEqual('a', gt('b')) // throws
assert.deepEqual(1, gt(0)) // passes
assert.deepEqual(0, gt(0)) // throws
assert.deepEqual([ 1, 2, 3 ], loose({ length: gt(1) })) // passes
assert.deepEqual([ 1 ], loose({ length: gt(1) })) // throws
gte (expected)
Checks if actual is greater than or equal to expected.
assert.deepEqual('b', gte('a')) // passes
assert.deepEqual('a', gte('b')) // throws
assert.deepEqual(1, gte(0)) // passes
assert.deepEqual(0, gte(0)) // passes
assert.deepEqual([ 1, 2, 3 ], loose({ length: gte(1) })) // passes
assert.deepEqual([ 1 ], loose({ length: gte(1) })) // passes
lt (expected)
Checks if actual is less than expected.
assert.deepEqual('a', lt('b')) // passes
assert.deepEqual('b', lt('a')) // throws
assert.deepEqual(0, lt(1)) // passes
assert.deepEqual(0, lt(0)) // throws
assert.deepEqual([ 1, 2, 3 ], loose({ length: lt(1) })) // throws
assert.deepEqual([ 1 ], loose({ length: lt(1) })) // throws
lte (expected)
Checks if actual is less than or equal to expected.
assert.deepEqual('a', lte('b')) // passes
assert.deepEqual('b', lte('a')) // throws
assert.deepEqual(0, lte(1)) // passes
assert.deepEqual(0, lte(0)) // passes
assert.deepEqual([ 1, 2, 3 ], loose({ length: lte(1) })) // throws
assert.deepEqual([ 1 ], loose({ length: lte(1) })) // passes
custom (expectedFn)
If expectedFn is not a function than this matcher falls back to strict
matcher. An actual value is passed to expectedFn to check.
expectedFn should return either boolean result or an object with
the match
and expected
fields. boolean match
property says whether check
passed and expected
is used in error reporting. It is possible to return from
custom expectedFn results of another matcher.
assert.deepEqual({ a: 1 }, custom( actual => actual.a === 1) ) // passes
assert.deepEqual({ a: 1 }, custom( actual => actual.a !== 1) ) // throws
assert.deepEqual({ a: 1 }, custom( actual => ({ // passes
match: actual.a === 1,
expected: 1,
}) ))
assert.deepEqual({ a: 1 }, custom( actual => ({ // throws
match: actual.a !== 1,
expected: '["a" should not be equal to 1]',
}) ))
// return results of another matcher
assert.deepEqual([1, 1, 1], custom( // passes
(actual, comparator) => arrayOf(gt(0)).match(actual, comparator)
))
assert.deepEqual([1, 1, 'a'], custom( // throws
(actual, comparator) => arrayOf(1).match(actual, comparator)
))
FAQ
Why enhancing assert with matchers?
There are cases when you care more not about specific values but rather about
their shapes or features. assert-match
provides you with a way to achieve that
through matchers.
Why yet another matchers?
Existing assertion libraries provide you with tons of crazy named matchers and
each new use case requires them (or you) to introduce completely new matcher. On
the other hand assert-match
provides you with succinct set of combinable
matchers, sufficient to reproduce all the matchers of that libs in a clear way.
Why does matchers are strict by default?
The more strict your tests the less probability to introduce bugs into your
system and the more probability to detect them. However, as noted above, there
are cases when you care more not about specific values but rather about their
shapes or features. assert-match
tries to consistently address these two
points.
What about power-assert
?
Why no extension API?
For matchers to be combinable means that not many of them can not be expressed
by existing ones, so this feature would not be in great demand. Additionally,
custom
matcher may be used for this purpose to some extent. However, you are
always welcome to issues to provide your points why this or any other feature is
required.