Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mainser/cindel/llms.txt
Use this file to discover all available pages before exploring further.
Cindel provides Cindel.openInMemory specifically for tests and short-lived isolates. An in-memory database is backed by the same Rust native engine as a file-based database — you get the same typed collections, generated queries, transactions, and watchers — but all data is discarded when the database is closed and no files are written to disk. This makes tests fast, fully isolated, and free of cleanup code.
Use openInMemory for all unit and widget tests. It is faster than a file-backed database because no OPFS or filesystem I/O is involved, and it produces no test artifacts that need to be deleted.
Basic usage
import 'package:cindel/cindel.dart';
import 'package:test/test.dart';
test('stores users', () async {
final db = await Cindel.openInMemory(schemas: [UserSchema]);
addTearDown(db.close);
final user = User()
..email = 'ada@example.com'
..name = 'Ada';
await db.users.put(user);
expect(await db.users.get(user.dbId), isNotNull);
});
Register addTearDown(db.close) immediately after opening the database. The test framework calls it automatically after the test body completes, whether the test passes or fails, so the database is always released.
In-memory databases do not persist across close() and a subsequent open() call. Every openInMemory returns a fresh, empty database. If you need data from one test to be present in another, put it inside the same test or in a setUp block.
Cleanup in plain Dart tests
For plain Dart tests using the test package without Flutter’s addTearDown, use tearDown or tearDownAll:
import 'package:cindel/cindel.dart';
import 'package:test/test.dart';
void main() {
late CindelDatabase db;
setUp(() async {
db = await Cindel.openInMemory(schemas: [UserSchema]);
});
tearDown(() async {
await db.close();
});
test('returns null for missing id', () async {
final result = await db.users.get(999);
expect(result, isNull);
});
test('stores and retrieves a user', () async {
final user = User()
..email = 'grace@example.com'
..name = 'Grace Hopper';
await db.users.put(user);
final saved = await db.users.get(user.dbId);
expect(saved, isNotNull);
expect(saved!.email, 'grace@example.com');
});
}
Testing a specific backend
openInMemory defaults to the same backend as Cindel.open — MDBX on native platforms. To test the SQLite code path explicitly, pass backend: CindelStorageBackend.sqlite:
test('stores users on SQLite', () async {
final db = await Cindel.openInMemory(
schemas: [UserSchema],
backend: CindelStorageBackend.sqlite,
);
addTearDown(db.close);
final user = User()
..email = 'ada@example.com'
..name = 'Ada';
await db.users.put(user);
expect(await db.users.get(user.dbId), isNotNull);
});
This is useful when you want to verify that your queries and transactions behave identically on both backends, or when you are specifically targeting the SQLite path in production.
Testing watchers
Watchers work the same way with in-memory databases. Emit a change, then verify the stream:
test('watcher fires after put', () async {
final db = await Cindel.openInMemory(schemas: [UserSchema]);
addTearDown(db.close);
final user = User()
..email = 'ada@example.com'
..name = 'Ada';
await db.users.put(user);
final event = await db.users
.watchObject(user.dbId)
.first;
expect(event, isNotNull);
expect(event!.email, 'ada@example.com');
});
Testing migrations
Migrations require a file-backed database because they involve close, schema upgrade, and reopen. Use a temporary directory and clean it up after the test:
import 'dart:io';
import 'package:cindel/cindel.dart';
import 'package:test/test.dart';
test('migrates users from v1 to v2', () async {
final dir = await Directory.systemTemp.createTemp('cindel_test_');
addTearDown(() => dir.delete(recursive: true));
// Open with old schema and seed data.
final v1 = await Cindel.open(
directory: dir.path,
schemas: [OldUserSchema],
);
await v1.oldUsers.put(OldUser()..email = 'ada@example.com');
await v1.close();
// Reopen with migration plan and new schema.
final migrationPlan = CindelMigrationPlan(
targetVersion: 2,
baselineVersion: 1,
steps: [
CindelMigrationStep(
fromVersion: 1,
toVersion: 2,
openSchemas: [OldUserSchema],
targetSchemas: [UserSchema],
migrate: (context) async {
final oldUsers = await context.exportObjects(OldUserSchema);
await context.registerTargetSchemas();
await context.importObjects(
UserSchema,
oldUsers.map((old) => User.fromLegacy(old)),
);
},
),
],
);
final v2 = await Cindel.open(
directory: dir.path,
schemas: [UserSchema],
migrationPlan: migrationPlan,
);
addTearDown(v2.close);
final users = await v2.users.filter().findAll();
expect(users, hasLength(1));
expect(users.first.email, 'ada@example.com');
});
For testing the migration logic in isolation — without caring about persistence — open a fresh in-memory database and verify your transformation functions directly:
test('User.fromLegacy maps email correctly', () {
final old = OldUser()..email = 'ada@example.com';
final migrated = User.fromLegacy(old);
expect(migrated.email, 'ada@example.com');
});