Integration Test

Method

A test that wires several pieces of a system together as they really run and checks that the joints between them hold.

Why it exists

The basic way to check code is to take one piece on its own and feed it values. Give a discount function a price of $8 and ten percent off, get $7.20 back, and that function does its job. Checking a single piece this way is a unit test, and each one finishes in about a millisecond, so a few thousand of them run in under a minute.

The trouble is that every piece can be correct and the assembly still break. The discount function hands back the whole number 720. The column in the database that receives it is defined to hold two decimal places, and it reads that same number as 7.20. Each side follows its own spec, each side passes its own tests, and connecting them stores a hundredth of the right amount.

This kind of failure always showed up the same way. A support ticket says payments are wrong, the engineer who owns the payment code says their tests are all green, and the engineer who owns storage says the same thing. Neither is lying. Nobody was checking the space between them.

An integration test promotes that space to something you can check. Instead of isolating a piece, you leave it connected and run it, watching whether the shape of the values, the order they move in, and the way failures propagate actually line up. What's under test is not the inside of a component but the seam where two of them meet.

How to run it

Start by deciding how much to wire up. Connect everything and you have an end-to-end test; connect nothing and you have a unit test. The skill is picking the two or three pieces that actually drift apart.

  1. Choose the seam. Order handling and the database, or signup code and the mail-sending module — somewhere a value genuinely crosses from one thing to another.
  2. Cut off the outside world. You can't fire a real request at a card processor every time the suite runs, so you stand up a stub that always answers "approved." That stand-in is a mock. Keep the seam you care about real and fake only the parts that depend on someone else's server.
  3. Lay down a starting state. Rather than hand-building "two users and three orders already exist" every run, write it once and load it. Data prepared this way is a fixture.
  4. Run it connected and check the far side. What matters is not what the function returned but what the world looks like afterward — whether 720 actually landed in the column as 720.
  5. Clean up. Delete or roll back whatever the test created. Skip this and yesterday's leftover order fails today's run.

The suite gets wired to run automatically whenever a change is merged. The practice of merging often and verifying automatically is continuous integration, and integration tests usually run there, right after the unit tests.

When it fits and when it doesn't

Use them wherever a value crosses between two or more parts: code writing to a database, code calling an external interface, one service calling another. For something that stays inside a single piece — is this formula right? — a unit test is faster and more precise about what broke.

The payoff grows in systems split into many small services. More pieces means more seams, and more seams means more situations where each part is fine and the whole thing still doesn't work.

Three limits come with them. They're slow: spinning up a database, loading data, tearing it down again puts each case somewhere between half a second and several seconds, and two hundred integration tests routinely take longer than two thousand unit tests. They don't localize failures: you learn that something broke, not which side broke it, so a person still has to go in and look. And they turn unstable easily — once a test starts depending on execution order or leftover rows, it becomes a flaky test that passes some days and fails others with no code change in between.

So the move is to choose placements rather than pile up numbers. A common shape is a few thousand unit tests underneath and a few dozen integration tests sitting exactly where values cross.

How it sounds at work

"Units are all green but integration caught something. Looks like the order-storage side."

The per-piece checks passed and the seam failed, so attention moves to where the two meet.

"This should have been caught by an integration test, but that seam had no coverage."

An admission that no test existed at the point where the incident happened, and a commitment to add one next.

"Integration is past three minutes now, so I'm splitting it to run in parallel."

Verification has grown long enough that engineers are waiting on it, and the suite is being divided.

At Pagedeck this comes up whenever the document-saving code is touched. Theo changes the autosave interval, runs the unit tests, and everything is green — then one integration case stays red. Two save requests arriving within the same second means the second one overwrites the record the first one just created.

That failure is invisible from the saving code alone. It only appears when the code and the database are genuinely connected and requests arrive back to back. Once Theo fixes the seam the case turns green, and the test stays there to stop the next person who touches that spot.

What people get wrong

The most common mistake is reading integration tests as an upgraded version of unit tests. A wider view feels like a better one. But the two are layers, not ranks. The wider you look, the closer you get to reality and the slower and vaguer the diagnosis becomes. Take it as a ranking, write several hundred integration tests, and a full pass takes twenty minutes — at which point people stop waiting for the result before merging, and the suite has lost its job.

Assuming that closer to the real environment is always better has the same shape. Pointing the test at the live card processor sounds more trustworthy, until their server is slow one morning and your suite fails for reasons that have nothing to do with your code, with transaction fees attached. Using a stand-in isn't laziness — it's how you keep the seam under test and nothing else. Checks that reach all the way out to real third parties get run separately, a handful of them, just before a release.

Easily confused with

TermWhat stays connectedHow it differs
Unit testNothing — one piece aloneChecks the logic inside a piece. Runs in about a millisecond and points straight at the culprit when it fails, but never catches two pieces disagreeing
End-to-end testEverything, browser includedClicks through login to checkout the way a person would. Closest to reality, but each case takes around ten seconds and a single changed label breaks it

Where to go next

  • Testing

    see the full set of layers this one sits between

  • Unit test

    the layer directly below, which makes the boundary easy to see

Last updated 2026-09-16