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.
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:
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.
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') // passesStep 3: Deep Equality for Objects and Arrays
Use deepEqual to compare objects and arrays by structure rather than reference.
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' },
) // passesStep 4: Test for Exceptions
Use throws to verify that a function throws an error, and rejects for async functions that should reject.
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.
import { match, doesNotMatch } from 'remix/assert'
match('hello world', /hello/) // passes
match('user@example.com', /@/) // passes
doesNotMatch('hello', /goodbye/) // passesStep 6: Mark Unreachable Code
Use fail to mark code paths that should never execute. This is useful in switch statements and error handling.
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.
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:
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
| Pattern | Function | Example |
|---|---|---|
| Truthy check | assert / ok | assert(user != null) |
| Equality | equal / strictEqual | strictEqual(status, 200) |
| Deep equality | deepEqual | deepEqual(obj, expected) |
| Throws | throws / rejects | throws(() => badFn()) |
| Pattern match | match | match(email, /@/) |
| Unreachable | fail | fail('Should not reach here') |
Next Steps
- Use the test package to run tests with these assertions
- See the API Reference for all function signatures