TL;DR
Coming from Django, I missed the “it just works” test database story: tests can run real queries against a real DB with migrations applied automatically. In the TypeScript ecosystem, this is usually not “default behavior”, so I built a small helper for NestJS + MikroORM that gives me a Django-like workflow for integration tests while keeping unit tests fast and DB-free.
Background: what I miss from Django testing
In Django, the test runner and ORM are part of one cohesive stack:
- A dedicated test database is created automatically.
- Migrations (or schema setup) are handled for you.
- Each test (or test class) gets isolation via transactions / database flush strategies.
- The whole thing is standardized across most Django projects.
In many TS backends (NestJS + ORM + Jest/Vitest), the ecosystem is more modular. NestJS doesn’t own your ORM; the ORM doesn’t own your test runner; and the test runner doesn’t own your DB lifecycle. Result: you assemble your own conventions.
Why this isn’t “built-in” in many TS stacks (likely reasons)
A few reasons (none are “bad”, it’s mostly ecosystem shape):
- Framework/ORM/test-runner separation
NestJS is framework-only; MikroORM/Prisma are external; Jest/Vitest are external. No single layer feels responsible for end-to-end test DB lifecycle.
- Performance expectations differ
JS/ S culture often optimizes for fast unit tests first; integration tests exist but are intentionally explicit/opt-in.
- Parallelism & isolation are harder than they look
A “shared test DB” is easy; a “shared test DB with parallel test workers, deterministic cleanup, and fast migrations” is not trivial to implement.
So instead of waiting for a universal solution, I created a small helper that matches my needs.
The PoC: DatabaseTest helper
In this PR, I added a tiny utility class that:
- connects to a dedicated
*_test database,
- creates the DB if it doesn’t exist,
- runs migrations,
- and lets only certain test files opt in. 
Key idea: unit tests stay unit tests (fast, mocked). Integration tests opt into “real DB”.
The code
The helper lives at test/utils/database-test.ts and builds a MikroORM config from your app config, but with a test dbName and the migrator enabled. It ensures the database exists and migrates it.
Important implementation details from the PoC:
dbName becomes ${commonMikroOrmConfig.dbName}_test
extensions: [Migrator] enables migrations
ensureDatabase() creates the DB if missing
getMigrator().up() applies pending migrations
teardown closes the connection but keeps the database content
Using it in tests
I added posts.integration.spec.ts as a minimal integration test showing how it feels to use: 
beforeAll: orm = await DatabaseTest.init()
afterAll: await DatabaseTest.close()
- create an entity, persistAndFlush, clear EM, query it back, assert.
This test demonstrates real persistence + retrieval via the DB.
It also includes a second test that asserts data is still there (“keeps data between tests”). This is just here to demonstrate the default behavior but in a real world project it would be a good idea to delete the created data after each test case.
What I like about this approach (benefits)
- Django-ish developer experience for integration tests: “run tests, DB exists, migrations applied”.
- Opt-in integration tests: you only pay the DB cost where it matters.
- No custom CLI needed: the helper is regular TypeScript code used by Jest specs.
- Avoids over-mocking: you can test queries, relations, constraints, migrations… the real stuff.
Current trade-offs
This is explicitly a PoC, and it has important caveats:
- No isolation by default
The DB content is kept between tests and between runs.
That’s fast, but it can create inter-test coupling unless you manage cleanup carefully.
- No parallel-worker safety
If you run tests with multiple workers, they may collide on the same DB.
- Migration runtime
Running migrations on test start is great, but can be slow as the migration history grows.
- State drift risk
“Kept DB” can hide bugs if your schema/data evolves but the DB wasn’t reset as you expected.
What I would implement
If I were to evolve this from PoC to “real project quality”, I’d add:
- Isolation strategy: truncate tables between tests/suites
- Parallel test support: per-worker DB name (e.g. _test_w1, _test_w2).
- Seeding/factories: a standard place for fixtures, factories, and deterministic seeds.
Closing
Django made “real DB tests” feel boring—in a good way. In NestJS + MikroORM, I had to assemble the story myself. This helper is small, explicit, and already valuable for integration tests where real queries matter.
PoC source: PR #1 in abel-castro/blog-nest .