Database testing
Skill almasumdev/awesome-mobile-testing-agent-skills/.github/skills/integration/database-testing
Expert guidance on testing persistence layers (Room, SQLite, SQLDelight, Core Data, Drift, WatermelonDB) using in-memory databases and migration tests. Use when asked to test a DAO, a query, or a schema migration.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-testing-agent-skills --skill database-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.5 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Database Testing
Instructions
Database tests are the second-most-valuable layer after unit tests. They catch the bugs unit tests cannot see: wrong SQL, broken migrations, missing indices, and type-mapping errors. Keep them fast and hermetic by running against in-memory databases whenever the driver supports it.
1. Pick the Right Harness per Stack
- Android + Room —
Room.inMemoryDatabaseBuilder(...)on the host via Robolectric, or on-device withandroidx.test.ext.junit. Prefer JVM + Robolectric for iteration speed. - Android + SQLDelight —
JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)on JVM, no emulator required. - Kotlin Multiplatform (SQLDelight) — use the JVM driver for
commonTest; native driver only for target-specific edge cases. - iOS + SQLite / GRDB — in-memory
DatabaseQueue(DatabaseQueue()without a path). - iOS + Core Data —
NSPersistentStoreDescriptionwithNSInMemoryStoreTypein a dedicated test container. - Flutter + Drift —
NativeDatabase.memory()orDriftIsolatewith a memory executor. - Flutter + sqflite —
databaseFactoryFfiwithinMemoryDatabasePathandsqflite_common_ffi. - React Native + WatermelonDB —
LokiJSAdapterwithuseWebWorker: falseanddbNameunique per test.
2. DAO Test — Android Room (JVM via Robolectric)
@RunWith(AndroidJUnit4::class)
@Config(manifest = Config.NONE)
class OrderDaoTest {
private lateinit var db: AppDatabase
private lateinit var dao: OrderDao
@Before fun setUp() {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(), AppDatabase::class.java
).allowMainThreadQueries().build()
dao = db.orderDao()
}
@After fun tearDown() = db.close()
@Test fun insert_and_load() = runTest {
dao.insert(OrderEntity(id = 1, total = 1999))
assertEquals(1999, dao.load(id = 1)!!.total)
}
}
3. DAO Test — iOS + GRDB
final class OrderDaoTests: XCTestCase {
var db: DatabaseQueue!
override func setUp() async throws {
db = try DatabaseQueue() // in-memory
try await AppSchema.migrator.migrate(db)
}
func test_insertAndLoad() async throws {
let dao = OrderDao(db: db)
try await dao.insert(Order(id: 1, totalCents: 1999))
let loaded = try await dao.load(id: 1)
XCTAssertEqual(loaded?.totalCents, 1999)
}
}
4. DAO Test — Flutter + Drift
void main() {
late AppDb db;
setUp(() => db = AppDb(NativeDatabase.memory()));
tearDown(() async => db.close());
test('insert and load order', () async {
await db.into(db.orders).insert(OrdersCompanion.insert(id: 1, totalCents: 1999));
final row = await (db.select(db.orders)..where((t) => t.id.equals(1))).getSingle();
expect(row.totalCents, 1999);
});
}
5. DAO Test — React Native + WatermelonDB
import { Database } from '@nozbe/watermelondb';
import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs';
const makeDb = () => new Database({
adapter: new LokiJSAdapter({ schema, useWebWorker: false, useIncrementalIndexedDB: false, dbName: `test_${Math.random()}` }),
modelClasses: [Order],
});
test('insert and load order', async () => {
const db = makeDb();
await db.write(async () => {
await db.get<Order>('orders').create((o) => { o.orderId = 1; o.totalCents = 1999; });
});
const loaded = await db.get<Order>('orders').query().fetch();
expect(loaded[0].totalCents).toBe(1999);
});
6. Migration Tests
Migrations are where database tests earn their keep.
Room: MigrationTestHelper creates a DB at version N, runs the migration, and asserts the resulting schema is identical to the auto-generated N+1 schema (schemas/ directory with exportSchema = true).
Core Data: build a .sqlite seeded with old data, apply the mapping model, assert the new entities contain the expected data.
SQLDelight / Drift: ship N.sqm / migration strategies; write tests that open the DB at the old schema, insert representative rows, run schemaVersion-aware migration, and re-read.
Every destructive migration must have at least one test that seeds realistic old-shape rows and verifies no data loss where none is intended.
7. Transactions and Concurrency
- Test that a failed step rolls back: wrap two inserts in a transaction, have the second throw, assert the first is absent.
- Test that a query running concurrently with a write does not see partial state (where your driver guarantees it).
8. What to Avoid
- Shared singleton DB across tests — flakiness guaranteed. Always build and tear down per test.
- Writing to the user's real app directory from tests. Use memory or a per-test temp directory.
- Asserting on SQL strings. Assert on resulting rows.
9. Checklist
- DB is fresh per test (in-memory or temp file, closed in teardown).
- Every migration has a test with realistic seeded data.
- Schema is exported and diffed (
exportSchema = truefor Room; equivalent for SQLDelight / Drift). - Transactions are covered: both commit and rollback paths.
- No test depends on the execution order of another.
- DAO tests run on the host JVM / host Swift / Dart VM / Node — not on emulator — when the driver allows.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.