Skip to content

Test Overview

The test package is a built-in test runner for Remix applications. It provides a familiar describe/it API for structuring tests, lifecycle hooks for setup and teardown, mock functions for isolating dependencies, and spies for observing calls to existing functions. No external test framework (like Jest or Vitest) is needed.

When to Use This Package

Use the test package to:

  • Write unit tests for utility functions, data loaders, and actions
  • Test route handlers by sending simulated Request objects
  • Mock external services and dependencies
  • Run tests in the same runtime your application uses

Quick Example

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

describe('add', () => {
  it('adds two numbers', (t) => {
    t.assert.equal(1 + 1, 2)
  })

  it('handles negative numbers', (t) => {
    t.assert.equal(-1 + 1, 0)
  })
})

Run tests with:

bash
npx remix test

Key Concepts

  • describe / suite -- Groups related tests into a block. Can be nested to any depth. suite is an alias for describe.
  • it -- Defines a single test case. Receives a test context t with assertion and mocking utilities.
  • Test context (t) -- Every test callback receives t with t.assert (assertions), t.mock (create mocks), and t.spyOn (spy on methods).
  • Lifecycle hooks -- before/after run once per describe block. beforeEach/afterEach run before/after every test.
  • Mocks -- Functions that record their calls and can be configured to return specific values.
  • Spies -- Wrappers around real functions that record calls while still executing the original.

Test Structure

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

describe('UserService', () => {
  before(async () => {
    // Runs once before all tests in this block
    await setupDatabase()
  })

  afterEach(async () => {
    // Runs after each test
    await clearTestData()
  })

  it('creates a user', async (t) => {
    let user = await createUser({ name: 'Alice' })
    t.assert.ok(user.id)
    t.assert.equal(user.name, 'Alice')
  })

  it('rejects duplicate emails', async (t) => {
    await createUser({ email: 'a@b.com' })
    await t.assert.rejects(() => createUser({ email: 'a@b.com' }))
  })
})

Mocking and Spying

ts
it('calls the handler', (t) => {
  // Create a mock function
  let handler = t.mock()

  emitter.on('event', handler)
  emitter.emit('event', 'data')

  t.assert.equal(handler.calls.length, 1)
  t.assert.deepEqual(handler.calls[0].arguments, ['data'])
})

it('tracks method calls', (t) => {
  let spy = t.spyOn(logger, 'info')

  doSomething()

  t.assert.equal(spy.calls.length, 1)
})
  • assert -- The assertion functions available via t.assert
  • API Reference -- Full API documentation

Released under the MIT License.