JSPM

expect-asymmetric

1.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • 0
  • Score
    100M100P100Q32449F
  • License MIT

Asymmetric matchers for expect (jest/vitest)

Package Exports

  • expect-asymmetric
  • expect-asymmetric/dist/index.js
  • expect-asymmetric/dist/index.mjs

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (expect-asymmetric) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

expect-asymmetric

Asymmetric matchers for expect (Jest/Vitest)

These matchers work inside expect.toEqual() / expect.toMatchObject(), letting you ignore noise (IDs, dates, timestamps, etc.) and assert only what matters.

Asymmetric matchers are the same kind of objects you get from expect.stringContaining() or expect.any(Number) — but here you can define your own.

Install

$ npm install --save-dev expect-asymmetric
$ yarn add -D expect-asymmetric
$ pnpm add -D expect-asymmetric

Usage

import ms from 'ms'
import { expect, test } from 'vitest'
import matchers from 'expect-asymmetric'

test('example with custom matchers', () => {
  const result = {
    id: 'f93f5f83-64c0-4f57-9f53-542c9055bb3f',
    createdAt: new Date(),
    email: 'user@example.com',
  }

  expect(result).toEqual({
    id: matchers.stringUUID(),
    createdAt: matchers.dateWithin(new Date(), ms('2s')),
    email: matchers.stringMatching(/@example\.com$/),
  })
})

Matchers

You can import matchers from this package either by:

  • As a complete import (this is how the documentation is written):
import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("Hello, world!").toEqual(matchers.stringStartsWith("Hello"))
  • Or as individual functions:
import { expect } from 'vitest'
import { stringStartsWith } from 'expect-asymmetric'

expect("Hello, world!").toEqual(stringStartsWith("Hello"))

Strings

stringEquals(expected: string | string[]): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("Hello, world!").toEqual(matchers.stringEquals("Hello, world!"))

// This is identical to:
expect("Hello, world!").toEqual("Hello, world!")
// But provided so you can use matchers.not with this

stringStartsWith(prefix: string): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("Hello, world!").toEqual(matchers.stringStartsWith("Hello"))

stringEndsWith(suffix: string): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("Hello, world!").toEqual(matchers.stringEndsWith("world!"))

stringIncludes(contains: string): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("Hello, world!").toEqual(matchers.stringIncludes('ello, world'))

stringMatches(pattern: RegExp): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("Hello, world!").toEqual(matchers.stringMatches(/hello/i))

stringBase64Encoded(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("WW91IGFyZSB0aGUgT25lLCBOZW8=").toEqual(matchers.stringBase64Encoded())

stringDateISO8601(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("YYYY-MM-DDTHH:mm:ss.sssZ").toEqual(matchers.stringDateISO8601())

stringEmail(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("noreply+jdrydn@github.io").toEqual(matchers.stringEmail())

stringJSON(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect('{"hello": "world"}').toEqual(matchers.stringJSON())

stringJWT(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const jwt = 'eyJhbGciOiJIUzI1NiJ9.RlJFRSBZT1VSIE1JTkQ.edKefjjRqI9yxTTT6vpuWti040WoGzX5a-ofc36-acg'
expect(jwt).toEqual(matchers.stringJWT())

stringMD5(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const md5 = '064ff83b22543c064ff83b22543ca5e9f7cfa1d702daa9a5e9f7cfa1d702daa9'
expect(md5).toEqual(matchers.stringMD5())

stringSHA1(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const sha1 = '467d6b9adfe97245b6504467d6b9adfe97245b65049168be0c26da8fe8d749168be0c26da8fe8d74'
expect(sha1).toEqual(matchers.stringSHA1())

stringULID(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect('01ARZ3NDEKTSV4RRFFQ69G5FAV').toEqual(matchers.stringULID())

stringUUID(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect('fb840792-9595-11f0-8de9-0242ac120002').toEqual(matchers.stringUUID())

Numbers

numberEquals(expected: number | number[]): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(42).toEqual(matchers.numberEquals(42))
// This is identical to:
expect(42).toEqual(42)
// But provided so you can use matchers.not with this

numberGreaterThan(expected: number): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(42).toEqual(matchers.numberGreaterThan(40))

numberLessThan(expected: number): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(42).toEqual(matchers.numberLessThan(45))

numberBetween(min: number, max: number): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(42).toEqual(matchers.numberLessThan(40, 45))

numberFloat(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(3.14159265).toEqual(matchers.numberFloat())

Booleans

booleanEquals(expected: boolean): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(true).toEqual(matchers.booleanEquals(true))
// This is identical to:
expect(true).toEqual(true)
// But provided so you can use matchers.not with this

Dates

dateEquals(expected: Date | Date[]): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const now = new Date()
expect(now).toEqual(matchers.dateEquals(now))
// This is identical to:
expect(now).toEqual(now)
// But provided so you can use matchers.not with this

dateToday(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const today = new Date()
expect(today).toEqual(matchers.dateToday())
expect(today.toISOString()).toEqual(matchers.dateToday())

dateYesterday(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const yesterday = new Date(Date.now() - 86400 * 1000)
expect(yesterday).toEqual(matchers.dateYesterday())
expect(yesterday.toISOString()).toEqual(matchers.dateYesterday())

dateGreaterThan(expected: Date): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const now = new Date('2025-09-25T21:45:00.000Z')
const then = new Date('2025-09-25T20:00:00.000Z')
expect(now).dateGreaterThan(matchers.dateGreaterThan(then))

dateLessThan(expected: Date): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const now = new Date('2025-09-25T21:45:00.000Z')
const then = new Date('2025-09-25T22:00:00.000Z')
expect(now).dateGreaterThan(matchers.dateLessThan(then))

dateWithin(target: Date, tolerance: number): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'
import ms from 'ms'

const today = new Date()
expect(today).toEqual(matchers.dateWithin(today, ms('20m')))
expect(today.toISOString()).toEqual(matchers.dateWithin(today, ms('20m')))

Collections

arrayContains(expected: unknown): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect([4, 8, 15, 16, 23, 42]).toEqual(matchers.arrayContains(42))

arrayEmpty(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect([]).toEqual(matchers.arrayEmpty())

objectEmpty(): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect({}).toEqual(matchers.objectEmpty())

objectHasProperty(key: string): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect({ hello: 'world' }).toEqual(matchers.objectHasProperty('hello'))

objectWithProperty(key: string, value: unknown): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect({ hello: 'world' }).toEqual(matchers.objectWithProperty('hello', 'world'))

Compose

and(matchers: AsymmetricMatcher[]): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("someone@example.com").toEqual(matchers.and([
  matchers.stringStartsWith("someone@"),
  matchers.stringEndsWith("@example.com"),
]))

or(matchers: AsymmetricMatcher[]): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("someone@example.com").toEqual(matchers.or([
  matchers.stringEndsWith("@example.com"),
  matchers.stringEndsWith("@example.co.uk"),
  matchers.stringEndsWith("@example.local"),
]))

not(matchers: AsymmetricMatcher[]): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("someone@example.com").toEqual(matchers.not([
  matchers.stringEndsWith("@example.co.uk"),
  matchers.stringEndsWith("@example.local"),
]))

Custom

You can use custom validators to check specific values.

  • The description property will be shown to the developer when this matcher fails.

stringValidator(description: string, compare: (value: string) => boolean): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

const isCaseInsensitive = matches.stringValidator('isCaseInsensitive', (value) => value.toLowerCase() === value)
expect("hello world").toEqual(isCaseInsensitive)

This works great with validator.js, for example:

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'
import isSemVer from 'validator/es/lib/isSemVer';

expect("1.0.0").toEqual(matches.stringValidator('isSemVer', isSemVer))

numberValidator(description: string, compare: (value: number) => boolean): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("hello world").toEqual(matches.numberValidator('isOdd', (value) => value % 2 === 1))

booleanValidator(description: string, compare: (value: boolean) => boolean): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect("hello world").toEqual(matches.numberValidator('example', (value) => value === true))

customMatcher(description: string, compare: (value: unknown) => boolean): AsymmetricMatcher

import { expect } from 'vitest'
import matchers from 'expect-asymmetric'

expect(new CustomClass()).toEqual(matches.customMatcher('example', (value) => value instanceof CustomClass))

Notes

Any questions or suggestions please open an issue.