Test-Owned Data
also called Data Created By The Test, Per-Test Tenancy
Each test creating the exact data it needs through the product's own write path and disposing of it afterwards - replacing shared fixtures whose drift and coupling cause most cross-team test failures.
A team's tests depend on account 4471 having two unpaid invoices. Somebody fixes a bug by correcting that account's data and eleven tests in three repositories go red. Nobody can say which tests depend on which rows, so the fixture becomes frozen, and new tests are written against whatever else happens to exist.
Test-owned data inverts the dependency: the test creates its own preconditions, in seconds, through the same API a customer would use, and removes them when it finishes.
Why it matters
It is the property that makes a suite parallelisable, independent and readable. A test that creates its own state documents its preconditions in the test body rather than in tribal knowledge, can run concurrently with any other test, and cannot be broken by someone else's cleanup.
It also removes the queue. Where data is provisioned by request — a subsetting tool with a two-day turnaround, a ticket to a platform team — the wait converts a data need into a scheduling problem, and engineers respond by writing weaker tests against existing rows. The queue costs far more in skipped coverage than in the analyst time it consumes.
Implementation patterns
- Factories in test code that build domain objects with sensible defaults and explicit overrides, so a test states only what matters to it.
- Isolation by tenancy. Each test creates its own tenant, account or workspace; teardown deletes by that key, which is one operation and cannot miss rows.
- Creation through the product's write path, so a broken onboarding flow fails a thousand tests rather than hiding behind a seeded database.
- A schema or database per CI worker where tenancy is not available in the domain model, created in milliseconds from a template rather than migrated per run.
- A short list of named exceptions: legacy flows whose state genuinely cannot be created through an API keep a small curated dataset, documented as exceptions rather than pretended away.
Industry example
The pattern is standard in mature SaaS engineering, where multi-tenancy already exists in the domain model and per-test tenants are close to free. Marketplaces with long histories — an estate in the mould of eBay, with two decades of record shapes — keep a masked production clone alongside it, but for a different job: volume, cardinality and distribution for performance and migration rehearsal, which no factory reproduces. The two coexist, and the failure is using the clone as the functional source.
Failure scenarios
- Shared fixture coupling, where any correction breaks unrelated tests and the fixture freezes.
- Teardown that misses, leaving orphan rows that accumulate until a uniqueness constraint fires months later.
- Creation through direct SQL inserts, which bypasses validation and produces states the product cannot actually reach, so tests pass on impossible data.
- Tenant explosion in a shared environment nobody cleans, eventually slowing every query.
- Masked clones treated as anonymous, when consistent surrogates and intact quasi-identifiers keep the copy re-identifiable and therefore inside the scope of data-protection obligations.
Trade-offs
Creating data through the product costs test runtime — an onboarding flow may take a second or two — and it couples tests to the write API, so an API change touches many tests. Seeded fixtures are faster per test and cheaper to write. The trade is speed now against independence later, and it flips as soon as more than one team shares the environment, which is also when the debugging cost of shared data starts being paid weekly.
When not to use it
For a single-team codebase with a 20 minute suite and one environment, a seeded fixture plus a reset script is the right answer and per-test tenancy is ceremony. Equally, do not use test-owned creation for performance testing: there you need production-shaped volume and distribution, and a factory loop that creates 10m rows is both slow and unrepresentative.
Interview question
Q: A platform team runs a subsetting service with a two-day SLA for test data, feeding three shared environments from a nightly masked clone. What do you remove, what do you keep, and how do you argue it to the team that built the service?
What a strong answer covers: removing the queue and the shared functional dataset in favour of per-test tenancy · keeping the clone for volume and migration rehearsal · naming the legacy exceptions · and arguing with measurements — tickets served, median wait, tests disabled while waiting, reruns caused by shared data — plus the unpriced risk that a masked environment is still personal data with retention, access and breach obligations.
Quick check
Quiz: Why create test data through the product's API rather than by SQL insert? Because inserts bypass validation and can produce states the product cannot reach, so the test passes on impossible data.
Flashcard: What single property should test data have? — The test can create exactly what it needs in seconds without affecting any other test.