Testing
Test Ridu configuration, generated contracts, database behavior, HTTP clients, and authoring flows.
Test the smallest boundary that can catch the failure you care about. Keep access rules, hooks, and validators in fast Go tests. Add a database-backed test for adapter behavior, a live server for HTTP behavior, and a browser for authoring behavior.
Choose the smallest useful layer
| Layer | Use it for |
|---|---|
| Go unit/config | Access rules, hooks, validators, helpers, and config resolution |
plugintest |
Public plugin configuration, validation, Local API, and REST behavior |
| Generated-contract check | Missing or stale manifest, Go, OpenAPI, TypeScript, and admin output |
| Database integration | Queries, constraints, indexes, transactions, migrations, and recovery |
| Live HTTP transport | URLs, headers, cookies, request bodies, response envelopes, and cancellation |
| Browser | Authentication, fields, forms, server issues, persistence, and access-aware UI |
Start low in the table and add a higher layer only when the boundary matters.
Unit-test executable config
Access rules, hooks, task handlers, field helpers, and plugin validators are ordinary Go functions. Test their branches directly, then keep one resolution test that composes the application config:
package content
import (
"testing"
"github.com/riducms/ridu"
)
func TestConfigResolves(t *testing.T) {
resolved, err := ridu.Resolve(Config())
if err != nil {
t.Fatal(err)
}
if len(resolved.Collections) == 0 {
t.Fatal("expected at least one collection")
}
}Assert stable public behavior such as issue codes and paths, access decisions, resolved fields, and operation results. Use table-driven tests for role and operation matrices.
In-memory tests
Ridu does not publish an in-memory database adapter for applications. Unit-test pure policy and configuration without a store, then use a disposable instance of the same adapter you deploy for operation integration tests. This avoids passing tests against behavior your production database does not share.
Use plugintest for public plugin conformance
Plugin authors can exercise the shared backend contract with plugintest.Run:
func TestConformance(t *testing.T) {
plugintest.Run(t, plugintest.Fixture{
Plugin: plugin.New(),
Fields: []field.Definition{
plugin.Field("value", field.Required()),
},
ValidData: store.Values{"value": store.String("valid")},
InvalidData: store.Values{"value": store.String("")},
Compatibility: []plugintest.CompatibilityCase{
{RiduVersion: ridu.FrameworkVersion, Compatible: true},
},
})
}This covers config resolution, manifest encoding, compatibility, validation, Local API, and REST behavior. A field with an admin component also needs frontend type checks and a browser test. See Build a custom field.
Check generated output
Run these commands in CI after installing project dependencies:
npm run ridu -- generate --check
npm run ridu -- checkbun run ridu -- generate --check
bun run ridu -- checkpnpm run ridu generate --check
pnpm run ridu checkyarn run ridu generate --check
yarn run ridu checkgenerate --check reports missing or stale generated files without rewriting them. ridu check
also checks migration history, Go formatting, go vet, Go tests, and the configured TypeScript and
Svelte projects. Fix the source config and regenerate; do not hand-edit generated files.
Test PostgreSQL behavior
Use a disposable database or uniquely named schema for integration tests that depend on PostgreSQL queries, constraints, indexes, locks, concurrency, task leasing, or recovery. Pass its URL through a test-only environment variable, clean it after the test, and never point a test suite at a shared development or production database.
For schema changes, run ridu migrate verify to replay migration history in a shadow schema. See
PostgreSQL and Migrations.
Test SQLite behavior
Create each SQLite test database under t.TempDir(). Test the same transaction, migration, index,
and recovery paths your application uses. Do not place the file on a shared network filesystem or
use a passing SQLite test to claim multi-host support. See SQLite.
Test MongoDB behavior
Use a disposable database on the supported writable replica-set topology when a test depends on
transactions, BSON encoding, indexes, migrations, or recovery. A standalone local server cannot
exercise Ridu’s transaction path. Run ridu migrate verify with a credential allowed to create and
drop its temporary verification database. See MongoDB.
Test a live transport
Handler tests are useful for a focused route. Keep at least one test that starts the application on a loopback port and calls it through the generated SDK. Cover login cookies, errors, cancellation, and the methods your frontend actually uses.
Custom endpoint tests should also cover the wrong method, body limits, actor propagation, and calls
through PluginEndpointContext.Local.
Test authoring in a browser
Run browser tests against your generated admin. Prefer a few complete journeys over a large number of cosmetic assertions:
- sign in;
- create or edit a representative document;
- confirm validation and access behavior;
- reload and verify persisted values; and
- exercise any custom admin field or plugin.
Test the browsers your users rely on. Ridu’s own Chromium coverage does not replace an application’s browser matrix.
Build readable test data
Use stable IDs, timestamps, sort order, array/block keys, and named accounts for distinct roles. Create one valid and one invalid value for each custom validator, and include explicit draft, published, deleted, or versioned states only when the workflow needs them. Give every test its own storage root, namespace, port, schema, or database and clean it afterward.
Project commands
An ordinary project CI job usually needs:
go test ./...
npm run ridu -- generate --check
npm run ridu -- migrate verify
npm run ridu -- check
npm run ridu -- buildThe equivalent project-local commands work with Bun, pnpm, and Yarn. When one fails, identify the boundary first: configuration, generated output, operation behavior, database/migration behavior, HTTP encoding, or admin rendering. Troubleshooting follows the same split.