testing

Test components and API routes with everything already wired up.

Developer toolsClient ServerBuilt on vitest, @testing-library/react

The hard part of testing a component is rarely the test. It's setting up the providers, the query cache and the router it expects. kitr/testing renders components inside your real app, and sends requests to your API without starting a server.

Install

kitr add testing

First time? Run npm i -g kitr once. Inside a project, the global kitr runs your project's own version.

This installs Vitest, plus Testing Library and happy-dom when the project has a UI. It writes vitest.config.ts and an example test. Run the tests with:

pnpm test

Tests live next to the code (app/**/*.test.tsx) or under test/.

Components

// app/features/projects/project-list.test.tsx
import { screen } from '@testing-library/react'
import { createTestApp } from 'kitr/testing'
import { renderWithKitr } from 'kitr/testing/react'
import { config } from 'virtual:kitr/config'
import { providers } from 'virtual:kitr/providers'
import { expect, it } from 'vitest'
import { ProjectList } from './project-list'

it('lists projects', async () => {
  const app = await createTestApp({ providers, config })
  renderWithKitr(<ProjectList />, {
    app,
    queryData: [[['projects', 'list'], [{ id: '1', name: 'Alpha' }]]],
  })
  expect(await screen.findByText('Alpha')).toBeTruthy()
})

createTestApp boots an app with environment: 'test' and whatever providers and config you pass. The virtual:kitr/* modules give you the same ones your app uses; the kitr() plugin in vitest.config.ts provides them. renderWithKitr wraps the component in that app's providers, and queryData fills the query cache first, so the component renders data without any network.

To test a component that uses links or route hooks, pass a wrapper that adds a router with a memory history.

API routes

// test/projects.test.ts
import { createTestClient } from 'kitr/testing'
import { expect, it } from 'vitest'
import { server } from '../server/main'

it('creates a project', async () => {
  const client = createTestClient(server)
  const res = await client.post<{ id: string }>('/api/projects', { name: 'Alpha' })
  expect(res.status).toBeLessThan(300)
  expect(res.body.id).toBeTruthy()
})

it('rejects a short name', async () => {
  const res = await createTestClient(server).post('/api/projects', { name: 'A' })
  expect(res.status).toBe(422)
})

The client calls server.fetch() directly, so no port is opened. It also accepts any object with a fetch(request) method, or a URL string to test a running deployment. JSON bodies are sent and parsed for you, and res.response holds the raw Response.

Pass default headers once, for example to sign every request in:

const client = createTestClient(server, { headers: { authorization: `Bearer ${token}` } })

Faking the API

To test the frontend against an API that doesn't exist yet, or to force an error case, use mocks.