Testing
@adecore/database/testing has a transport that answers the whole protocol from memory. The demos on this site run on it, and an app can run its tests or its Storybook on it without a helper or a server.
import { fakeDatabaseTransport, type FakeDatabase, type FakeDatabaseTransportOptions, type FakeTable } from '@adecore/database/testing';fakeDatabaseTransport
const client = createDatabaseClient(
fakeDatabaseTransport({
databases: { '/data/shop.sqlite': shop },
latencyMs: 150
})
);It returns a DatabaseTransport, so it goes wherever the real one goes: createDatabaseClient, and then a DatabaseProvider.
FakeDatabaseTransportOptions:
| Option | |
|---|---|
databases | The data, keyed by the path of a SQLite connection or the host of a MySQL one. A connection to a key that is not here fails with connect-failed. The fake ignores tunnels and still looks a server up by host, so a connection through Docker finds the database stored under 127.0.0.1, the host a form gives it by default. |
latencyMs | Delays every answer. A cancel for a request that is still waiting answers it with cancelled. |
server | The ServerInfo every open and test reports. Derived from the engine when left out. |
containers | The DockerContainer list that discover answers with. Nothing when left out, so the Docker mode of a connection form has no containers to pick. |
The transport works on a copy of the data, so what you pass in never changes, and each transport starts from it. Edits made through apply live as long as the transport.
The data
A FakeDatabase is { schemas }: schema name to table name to a FakeTable. SQLite uses the schema main.
A FakeTable has:
| Field | |
|---|---|
columns | The ColumnInfo list. |
rows | One entry per row, its Values in the order of columns. |
primaryKey | Also the row key. A table without one, and without a unique index, is read only. |
kind | table when left out. A view cannot be edited. |
indexes, foreignKeys | What structure reports. |
ddl | The CREATE statement structure reports. |
const shop: FakeDatabase = {
schemas: {
main: {
customers: {
columns: [
{ name: 'id', type: 'integer', kind: 'integer', nullable: false, defaultValue: null, autoIncrement: true, generated: false, comment: null },
{ name: 'name', type: 'text', kind: 'text', nullable: false, defaultValue: null, autoIncrement: false, generated: false, comment: null }
],
primaryKey: ['id'],
rows: [
[1, 'Amara Okafor'],
[2, 'Bram de Vries']
]
}
}
}
};What it answers
The fake follows the protocol, including its failures:
open,test,close,schemas,tablesandstructureanswer from the data. The schemasinformation_schema,mysql,performance_schemaandsysare markedsystem.rowspages withoffsetandlimit, reportshasMoreand cuts text and bytes atcellLimitinto previews.cellreturns the whole value.applyruns insert, update and delete as one unit: if a change fails, none is kept. It fails withread-onlyon a read only connection,unsupportedon a view,no-row-keywithout a row key andconflict(withchange) when a key matches no row or more than one. A missing value takes its default, with auto increment numbering andCURRENT_TIMESTAMP.executeandpagereadSELECT * FROM <table>and answerunsupportedfor any other statement; see the limits below.executereportsinTransaction.transactionraises and lowers a flag thatexecutereports. It is not a transaction: a rollback does not undo a change.discoveranswers with thecontainersoption.export,sampleandimportanswerunsupported, since there are no files to write or read. Withfileson the provider the views offer an export that fails withunsupported. An app that tests its file dialogs answers those methods in a transport of its own that wraps the fake.cancelanswerscancelled: truefor a request that is still waiting outlatencyMs.
Limits
It is a fake, not an engine:
whereandorderByare not interpreted.rowsandcountreturn every row in stored order.executeandpageonly runSELECT * FROM <table>, on the selected schema (the first one until a call names another). Every other statement answers with anunsupportederror, and as in the protocol the first failed statement ends the list. That includes theALTER TABLEof the designer and theDROP TABLEof the explorer.- Indexes, foreign keys and uniqueness are reported by
structurebut only the primary key is enforced on insert.
Use the real helper against a SQLite file for anything that depends on SQL, such as the exact text of an error.
In a test
import { createDatabaseClient, DatabaseProvider, TableView } from '@adecore/database';
import { fakeDatabaseTransport } from '@adecore/database/testing';
const client = createDatabaseClient(fakeDatabaseTransport({ databases: { '/data/shop.sqlite': shop } }));
render(
<UIProvider i18n={i18n}>
<DatabaseProvider client={client}>
<TableView connection={connection} schema="main" table="customers" />
</DatabaseProvider>
</UIProvider>
);