Documentation Index Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Testing Remix applications is straightforward thanks to the web standards-based API. Since Remix uses the standard fetch() API, you can test your routes without any special test harness.
Testing Philosophy
Remix follows these testing principles:
Tests run from source - No build step required
Use standard APIs - Just use fetch() to test routes
Runtime-agnostic - Tests work the same across all runtimes
Fast feedback - Unit tests run in milliseconds
Unit Testing Routes
Test routes by calling router.fetch() with standard Request objects:
import { describe , it } from 'node:test'
import * as assert from 'node:assert/strict'
import { createRouter } from 'remix/fetch-router'
import { route } from 'remix/fetch-router/routes'
let routes = route ({
users: '/users' ,
user: '/users/:id' ,
})
let router = createRouter ()
router . map ( routes , {
actions: {
users () {
return Response . json ({ users: [{ id: '1' , name: 'Alice' }] })
},
user ({ params }) {
return Response . json ({ id: params . id , name: 'Alice' })
},
},
})
describe ( 'Users API' , () => {
it ( 'lists users' , async () => {
let response = await router . fetch ( 'http://api.example.com/users' )
assert . equal ( response . status , 200 )
let data = await response . json ()
assert . equal ( data . users . length , 1 )
assert . equal ( data . users [ 0 ]. name , 'Alice' )
})
it ( 'gets user by id' , async () => {
let response = await router . fetch ( 'http://api.example.com/users/1' )
assert . equal ( response . status , 200 )
let data = await response . json ()
assert . equal ( data . id , '1' )
assert . equal ( data . name , 'Alice' )
})
})
Testing with Different Methods
Test POST, PUT, DELETE requests:
it ( 'creates a user' , async () => {
let response = await router . fetch ( 'http://api.example.com/users' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name: 'Bob' , email: 'bob@example.com' }),
})
assert . equal ( response . status , 201 )
let data = await response . json ()
assert . equal ( data . name , 'Bob' )
})
it ( 'updates a user' , async () => {
let response = await router . fetch ( 'http://api.example.com/users/1' , {
method: 'PUT' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ name: 'Alice Updated' }),
})
assert . equal ( response . status , 200 )
})
it ( 'deletes a user' , async () => {
let response = await router . fetch ( 'http://api.example.com/users/1' , {
method: 'DELETE' ,
})
assert . equal ( response . status , 204 )
})
Testing Middleware
Test middleware in isolation:
import { describe , it } from 'node:test'
import * as assert from 'node:assert/strict'
import type { Middleware } from 'remix/fetch-router'
function auth () : Middleware {
return ( context , next ) => {
let token = context . headers . get ( 'Authorization' )
if ( ! token || token !== 'Bearer secret' ) {
return new Response ( 'Unauthorized' , { status: 401 })
}
return next ()
}
}
describe ( 'Auth middleware' , () => {
it ( 'rejects requests without token' , async () => {
let middleware = auth ()
let response = await middleware (
{ headers: new Headers () } as any ,
async () => new Response ( 'OK' )
)
assert . equal ( response . status , 401 )
})
it ( 'allows requests with valid token' , async () => {
let middleware = auth ()
let headers = new Headers ({ Authorization: 'Bearer secret' })
let response = await middleware (
{ headers } as any ,
async () => new Response ( 'OK' )
)
assert . equal ( response . status , 200 )
})
})
Testing Components
Test Remix components with the flush() method:
import { describe , it } from 'node:test'
import * as assert from 'node:assert/strict'
import { createRoot } from 'remix/component'
import type { Handle } from 'remix/component'
function Counter ( handle : Handle ) {
let count = 0
return () => (
< button
on = {{
click () {
count ++
handle . update ()
},
}}
>
Count : { count }
</ button >
)
}
describe ( 'Counter component' , () => {
it ( 'increments on click' , () => {
let container = document . createElement ( 'div' )
let root = createRoot ( container )
root . render (< Counter />)
root . flush ()
let button = container . querySelector ( 'button' )
assert . equal ( button ?. textContent , 'Count: 0' )
button ?. click ()
root . flush ()
assert . equal ( button ?. textContent , 'Count: 1' )
})
})
Testing Database Queries
Use in-memory SQLite for fast database tests:
import { Database } from 'better-sqlite3'
import { createDatabase , table , column as c } from 'remix/data-table'
import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
describe ( 'User queries' , () => {
it ( 'creates and finds users' , async () => {
// Create in-memory database
let sqlite = new Database ( ':memory:' )
let db = createDatabase ( createSqliteDatabaseAdapter ( sqlite ))
let users = table ({
name: 'users' ,
columns: {
id: c . integer (),
name: c . varchar ( 255 ),
email: c . varchar ( 255 ),
},
})
// Create table
sqlite . exec ( `
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
)
` )
// Test create
await db . create ( users , {
name: 'Alice' ,
email: 'alice@example.com' ,
})
// Test find
let user = await db . find ( users , { email: 'alice@example.com' })
assert . equal ( user ?. name , 'Alice' )
})
})
Integration Testing
Test full request/response cycles:
import { createServer } from 'remix/node-fetch-server'
describe ( 'Full application' , () => {
it ( 'handles complete request' , async () => {
let server = createServer ( router )
// Start server on random port
await new Promise < void >(( resolve ) => {
server . listen ( 0 , () => resolve ())
})
let address = server . address ()
let port = typeof address === 'object' ? address ?. port : 3000
// Make real HTTP request
let response = await fetch ( `http://localhost: ${ port } /users` )
assert . equal ( response . status , 200 )
server . close ()
})
})
Test Utilities
Create helper utilities for common test scenarios:
export function createTestRequest (
url : string ,
options ?: RequestInit
) : Request {
return new Request ( url , options )
}
export function createFormRequest (
url : string ,
data : Record < string , string >
) : Request {
let formData = new FormData ()
for ( let [ key , value ] of Object . entries ( data )) {
formData . append ( key , value )
}
return new Request ( url , {
method: 'POST' ,
body: formData ,
})
}
export async function expectJson ( response : Response ) {
assert . equal ( response . headers . get ( 'Content-Type' ), 'application/json' )
return await response . json ()
}
Running Tests
Node.js
Bun
Deno
Best Practices
Test behavior, not implementation
Use in-memory databases for fast tests
Test error cases and edge cases
Keep tests focused and isolated
Use descriptive test names
Mock external services
Component Testing Component API with flush() for testing
Data Table Testing database queries