Remix
Remix

assert

A compatible subset of node:assert/strict that works in any JavaScript environment, including browsers — plus a vitest-/jest-style expect API for tests that prefer chainable matchers.

Uses strict equality (===) for all comparisons — no type coercion.

Features

  • AssertionError — compatible with node:assert.AssertionError (actual, expected, operator, name)
  • assert.ok — truthy check
  • assert.equal / assert.notEqual — strict equality (=== / !==)
  • assert.deepEqual / assert.notDeepEqual — recursive strict deep equality
  • assert.match — string matches a regexp
  • assert.fail — unconditional failure
  • assert.throws — synchronous throw assertion with optional error validation
  • assert.rejects — async rejection assertion with optional error validation
  • expect(value) — chainable matchers with .not, .rejects, .resolves, plus expect.objectContaining(...) for partial matches

Installation

npm i remix

Usage

Mirrors node:assert/strict — uses strict equality (===), so 1 !== '1' and null !== undefined.

import assert from 'remix/assert'

assert.ok(true)
assert.equal(1, 1)
assert.equal(1, '1') // throws — different types
assert.notEqual('a', 'b')
assert.deepEqual({ a: 1 }, { a: 1 })
assert.deepEqual({ a: 1 }, { a: '1' }) // throws — different types
assert.match('hello world', /world/)
assert.fail('should not reach here')

await assert.rejects(() => Promise.reject(new Error('oops')))
assert.throws(() => {
  throw new TypeError('bad')
}, TypeError)

assert.throws(
  () => {
    let error = new Error('Invalid value') as Error & { code: string }
    error.code = 'ERR_INVALID_ARG_VALUE'
    throw error
  },
  { code: 'ERR_INVALID_ARG_VALUE', message: /Invalid value/ },
)

Named exports

Each assertion is also exported as a named function:

import {
  ok,
  assert, // alias of ok()
  equal,
  notEqual,
  deepEqual,
  notDeepEqual,
  match,
  fail,
  throws,
  rejects,
} from 'remix/assert'

expect

A vitest-/jest-style chainable matcher API on top of the same AssertionError. Use .not to negate, .rejects / .resolves to assert on a promise. Mock-aware matchers work with mock.fn() / mock.method() from remix/test.

import { expect } from 'remix/assert'

expect(value).toBe(42)
expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 })
expect({ a: 1, b: 2 }).toEqual(expect.objectContaining({ a: 1 }))
expect({ a: { b: 1, c: 2 } }).toMatchObject({ a: { b: 1 } })
expect(value).not.toBeNull()
expect(arr).toHaveLength(3)
expect(spy).toHaveBeenCalledWith('hello', 1)

await expect(fetch('/missing')).rejects.toThrow('Not found')
await expect(loadModule()).resolves.toBeUndefined()

Available matchers:

Group Matchers
Equality toBe, toEqual, toBeNull, toBeUndefined, toBeDefined, toBeTruthy, toBeInstanceOf
Numbers toBeGreaterThan, toBeGreaterThanOrEqual, toBeLessThan, toBeLessThanOrEqual, toBeCloseTo
String/iterable toContain, toMatch, toHaveLength
Object shape toHaveProperty(path, value?), toMatchObject(partial)
Throwing toThrow(expected?)
Mock-aware toHaveBeenCalled, toHaveBeenCalledTimes(n), toHaveBeenCalledWith(...args), toHaveBeenNthCalledWith(nth, ...args)

expect.objectContaining(partial) is an asymmetric matcher recognized by toEqual (and any matcher that uses deep equality under the hood). It passes when the actual value has at least the keys in partial with matching values — extra keys are allowed.

License

See LICENSE