Fixture
Artifact
The state and data laid down before a test runs, pulled out of the test body and kept separately so every run starts from the same conditions.
Why it exists
Test code spends far more lines preparing than checking. To verify permissions on "a team with three documents and two invited members," the check is one line while creating the team, inviting two people and inserting three documents takes about twenty.
Those twenty lines used to be retyped in every test. Fifty similar tests meant fifty copies of similar setup. The trouble is that setup code follows the product. When Theo added an owner field to documents at Pagedeck, the feature worked fine and forty tests went red at once, because the forty places that said "create a document" were all older code that knew nothing about the new field. What broke was the setup, not the product, and Theo spent half a day making the same edit forty times.
Reading was hard too. When twenty-five lines of a thirty-line test are setup, you have to scroll to the bottom to learn what the test actually checks.
A fixture is that block of preparation lifted out of the test body, given a name, and kept in one place. Fifty tests call the same copy, so a product change has one place to fix, and the test body is left with the lines that do the checking.
What goes in it
A fixture is made of four things: a name, data, procedures, and a scope.
- Name — what this state is called. Write
team_with_three_docsand the test body tells you what is in place from the name alone. Call itdata1and you end up opening the definition anyway, which throws away the benefit of separating it. - Initial data — the values that already exist when the test starts. Two users, three documents, their permissions and creation times. It can be written as code or pulled out into a JSON file.
- Setup and teardown — the actions that actually lay the data down and clear it away. Inserting rows into the database or starting a test server is setup; removing those rows and stopping that server is teardown. Skip teardown and the three documents from the previous test survive into the next one, so a test expecting one document sees four and fails. A test that passes alone and breaks in a full run usually traces back here.
- Scope — how often the preparation is redone. Rebuilding it per test removes interference but runs slowly; building it once per file or per run is fast but carries whatever the previous test touched into the next. Setup that is only read can be built once; setup that tests modify gets rebuilt every time.
When several near-identical copies are needed, write a function that builds them instead of copying the fixture. Defaults are fixed in advance and the caller passes only the values it wants changed. An "expired invitation" passes an expiry time and takes the default for the other ten fields, which leaves only the value that matters in the test body.
Who writes it and who reads it
The person who writes a fixture is the developer writing that test, and it doesn't start out shared. The usual order is to keep the setup inside your own test, then move it to a shared location once another test needs the same state. Promote setup that has exactly one user and the second user will find the shape doesn't fit, so it gets rewritten anyway.
Three groups read it. Developers working in the same area check whether a usable fixture already exists before writing a new test. Whoever does the code review checks whether the test body alone makes its assumptions clear, and a name that doesn't describe the state gets caught here. A QA engineer reads which situations the automated tests already set up and verify, then picks the manual checks that sit outside them.
Ownership blurs easily on this kind of asset. When eighty tests depend on one shared fixture, nobody can tell which of them a single field change would break, so people build a near-duplicate instead. The fixtures multiply, differing in name and overlapping in content. Writing one line about what state the fixture guarantees, at the moment it becomes shared, lets the next person tell which values are safe to change and which are not.
How it sounds at work
"Could you add a permissions field to that test fixture? My case needs that value to run."
A request to add one item to shared setup data.
"That fixture is too heavy. It starts with 200 documents and the test only looks at two of them."
The setup data grew beyond what's needed, which made things slow and hid what the test assumes.
"It passes locally but breaks in the full run. I think the fixture isn't cleaning up."
A diagnosis that data from an earlier test is carrying over.
At Pagedeck the word comes up whenever a new feature gets tests. Theo, preparing "a share link that has already expired" for the expiry tests, found that the invitation tests already had something similar, promoted it, and now both use the same one. The tests that were already using that fixture go into the same pull request, since setup and the tests that depend on it have to change together.
What people get wrong
It's easy to assume that setup closer to the real service is better setup — copy the production database in and nothing gets missed. It works the other way around. A test that starts with 200 documents stretches the time a continuous integration (CI) run takes, and worse, nobody can tell what the test assumes any more. If it was passing because of the state of one document among the 200, the day that document changes costs a full day of hunting.
The other common one is treating a fixture as something you build once and never touch. It looks like a side artifact rather than product code, so it falls out of maintenance. It ages with the product all the same. Fields get added and defaults change while the setup data keeps its old shape, and the test goes on verifying a state that no longer exists in reality, reporting green the whole time. A broken feature with a passing test is more dangerous than no test at all — without one, at least somebody checks by hand.
Easily confused with
| Term | What it stands in for | How it differs |
|---|---|---|
| Mock | A fake object that returns a fixed answer when called | A mock replaces behavior during the test; a fixture builds the state it starts from. Answering "success" instead of calling the payment provider is a mock; having the document and the user already there to pay for is a fixture |
Where to go next
Last updated 2026-09-16