Skip to content

Assert Tutorial ​

This tutorial shows you how to use the assert package for testing and runtime validation. You will learn the most common assertion patterns and when to use each function.

Prerequisites ​

  • A Remix V3 project

Step 1: Basic Truthy Assertions ​

The assert function (and its alias ok) checks that a value is truthy. If it is falsy, an AssertionError is thrown with the provided message.

ts
import { assert, ok } from 'remix/assert'

// These pass
assert(true)
assert(1)
assert('non-empty string')
assert([1, 2, 3].length > 0, 'Array should not be empty')

// These throw AssertionError
assert(false)
assert(null, 'Value must not be null')
ok(undefined, 'Value must be defined')

Use assert in application code to enforce preconditions:

ts
import { assert } from 'remix/assert'

function getUser(id: string) {
  assert(id, 'User ID is required')
  assert(id.length > 0, 'User ID must not be empty')
  // ...
}

Step 2: Equality Checks ​

Use equal for loose equality and strictEqual for strict equality. In most cases, prefer strictEqual.

ts
import { equal, strictEqual, notEqual, notStrictEqual } from 'remix/assert'

// Loose equality (==)
equal(1, 1)       // passes
equal(1, '1')     // passes (type coercion)

// Strict equality (===)
strictEqual(1, 1)     // passes
// strictEqual(1, '1') // throws -- different types

// Negated versions
notEqual(1, 2)           // passes
notStrictEqual('a', 'b') // passes

Step 3: Deep Equality for Objects and Arrays ​

Use deepEqual to compare objects and arrays by structure rather than reference.

ts
import { deepEqual, notDeepEqual } from 'remix/assert'

// Objects
deepEqual(
  { name: 'Alice', age: 30 },
  { name: 'Alice', age: 30 },
) // passes -- same structure

// Arrays
deepEqual([1, 2, 3], [1, 2, 3]) // passes

// Nested structures
deepEqual(
  { users: [{ name: 'Alice' }] },
  { users: [{ name: 'Alice' }] },
) // passes

// These are NOT deep equal
notDeepEqual(
  { name: 'Alice' },
  { name: 'Bob' },
) // passes

Step 4: Test for Exceptions ​

Use throws to verify that a function throws an error, and rejects for async functions that should reject.

ts
import { throws, doesNotThrow, rejects, doesNotReject } from 'remix/assert'

// Check that a function throws
throws(() => {
  JSON.parse('invalid json')
})

// Check that it throws a specific error type
throws(() => {
  JSON.parse('invalid json')
}, SyntaxError)

// Check that a function does NOT throw
doesNotThrow(() => {
  JSON.parse('{"valid": true}')
})

// Async: check that a promise rejects
await rejects(async () => {
  await fetch('https://nonexistent.invalid')
})

// Async: check that a promise resolves
await doesNotReject(async () => {
  return 'success'
})

Step 5: Pattern Matching with Strings ​

Use match and doesNotMatch to test strings against regular expressions.

ts
import { match, doesNotMatch } from 'remix/assert'

match('hello world', /hello/)         // passes
match('user@example.com', /@/)        // passes
doesNotMatch('hello', /goodbye/)      // passes

Step 6: Mark Unreachable Code ​

Use fail to mark code paths that should never execute. This is useful in switch statements and error handling.

ts
import { fail } from 'remix/assert'

type Status = 'active' | 'inactive' | 'pending'

function getLabel(status: Status): string {
  switch (status) {
    case 'active':
      return 'Active'
    case 'inactive':
      return 'Inactive'
    case 'pending':
      return 'Pending'
    default:
      fail(`Unexpected status: ${status}`)
  }
}

Step 7: Use with the Test Package ​

The test package provides assertions through the test context t.assert. These are the same functions from this package.

ts
import { describe, it } from 'remix/test'

describe('math', () => {
  it('adds numbers', (t) => {
    t.assert.equal(1 + 1, 2)
    t.assert.strictEqual(typeof (1 + 1), 'number')
  })

  it('compares objects', (t) => {
    let result = { sum: 2 }
    t.assert.deepEqual(result, { sum: 2 })
  })
})

You can also import assertions directly for standalone use:

ts
import { describe, it } from 'remix/test'
import { equal, deepEqual } from 'remix/assert'

describe('math', () => {
  it('adds numbers', () => {
    equal(1 + 1, 2)
    deepEqual({ a: 1 }, { a: 1 })
  })
})

Summary ​

PatternFunctionExample
Truthy checkassert / okassert(user != null)
Equalityequal / strictEqualstrictEqual(status, 200)
Deep equalitydeepEqualdeepEqual(obj, expected)
Throwsthrows / rejectsthrows(() => badFn())
Pattern matchmatchmatch(email, /@/)
Unreachablefailfail('Should not reach here')

Next Steps ​

  • Use the test package to run tests with these assertions
  • See the API Reference for all function signatures

Released under the MIT License.