Testing
withRollback runs a callback inside a transaction that is always rolled back. Writes made inside are readable inside, so assertions can be made on them, and the graph is untouched afterwards.
import { withRollback } from 'mikro-orm-neo4j';
test('creates the edge', async () => {
await withRollback(orm, async (em) => {
em.create(Book, { id: '1', title: 'Graph Databases' });
await em.flush();
const found = await em.findOne(Book, { id: '1' });
expect(found?.title).toBe('Graph Databases');
});
});
Each call forks the EntityManager and opens its own transaction, so concurrent calls are independent and the suite does not have to run serially. A nested call is an independent scope rather than a savepoint — Neo4j has none.
It is a plain function taking a callback, deliberately: it references no test runner's globals and registers no lifecycle hooks, so a failing test points at the line that failed rather than at a wrapper installed somewhere else.
It returns whatever the callback returns, and rethrows whatever it throws — after rolling back in both cases.
A server for the suite
The driver's own tests run against a real Neo4j started with
Testcontainers, because the behaviours worth pinning — index creation,
cross-tenant isolation, cursor lifecycles, query plans — are behaviours of the server. A container per
suite plus withRollback per test keeps that fast enough to run on every commit.
const container = await new GenericContainer('neo4j:5.25')
.withExposedPorts(7687)
.withEnvironment({ NEO4J_AUTH: 'neo4j/test' })
.start();
Add NEO4J_PLUGINS: '["apoc"]' when the code under test calls APOC — or declare a
local fallback on the
routine and keep the plain image.