Everyone can draw the testing pyramid. Wide base of unit tests, a thinner band of integration, a sliver of end-to-end on top. Then the interviewer asks why those proportions, or drops the real one:
"Would you mock the database in this test?"
and the drawing stops helping.
My CMS has somewhere around 280 tests across the server and the client. So this is how I actually think about the split, and what each layer is allowed to fake.
The pyramid is a cost statement, not a shape
The shape isn't the point. The point is that each layer buys you a different amount of confidence at a different price to keep alive.
- Unit tests: fast, precise, cheap to maintain. A failure points at one function. But a green suite doesn't mean the app works, only that the pieces work alone.
- Integration tests: slower, and they prove the pieces actually fit, a route calls a service calls the database and the right thing comes back. Most of the bugs that would reach a user get caught here.
- End-to-end: a real browser driving the real app -- no mocks, no shortcuts. Highest confidence, and also slow, flaky, and expensive to keep green as the UI changes. Every e2e test is a small ongoing tax. So you write a lot of the cheap high-precision ones, a solid layer of the ones that catch real bugs, and you ration e2e to the flows where a silent break would be a genuine problem, login, checkout, publishing a post. The shape falls out of that math. It isn't a quota.

What to mock: one rule
Mock what you don't own and what's slow or nondeterministic. Third-party APIs, email sending, payment providers, the system clock, random values. Those get faked so the test is fast and gives the same answer every run.
Never mock the thing the test exists to check.
So, would you mock the database?
Depends what the test is for:
- Unit test of business logic: yes, mock the data layer. You're testing that the logic does the right thing given some rows, not that the query works.
- Integration test: no. Use a real database, a disposable test one. The entire reason the test exists is to prove your code and the database work together. Mock it and you've written a test of your mock, which passes whether or not the real query is broken. If you can say that distinction out loud, you're past the question.
The Postman version of the same question
"How do you turn manual Postman testing into something that runs in CI?" Same idea as an integration test: Postman is hitting a real API with real assertions, nothing mocked. You export the collection and run it in CI with Newman, the CLI runner, or better, move those assertions into your integration suite so they run alongside everything else instead of in a separate tool. The manual version was already an integration test, it just wasn't automated yet.
RTL: assert on what the user sees
React Testing Library pushes you to test rendered output and behavior, click this, see that, not component internals like state values or which hook fired.
The reason is maintenance. A test tied to internal state breaks when you refactor, even if the component still does exactly the same thing from the outside. A test tied to visible behavior only breaks when the behavior actually changes, which is when you want it to break.
When an e2e test goes flaky
Flaky almost always means one of three things:
- The test waits a fixed number of milliseconds instead of waiting for an actual condition (an element appearing, a response arriving)
- Tests share state, so order matters and a failure in one poisons the next
- It's hitting a real network or real time and getting different answers
The fixes line up with the causes: wait for conditions not timeouts, reset state between tests, and control anything nondeterministic like the clock. "Re-run it and it passes" is not a fix, it's the bug telling you it's still there.
Quick recap
- The pyramid shape comes from cost-to-maintain vs confidence-per-test, not from a rule about ratios
- Unit tests prove functions; integration tests prove the pieces fit and catch most user-facing bugs; e2e proves the whole flow and costs the most to keep
- Mock what you don't own and what's slow or nondeterministic; never mock the thing under test
- "Mock the database?" Yes in a unit test of logic, no in an integration test, where the DB integration is the point
- RTL tests target visible behavior so refactors don't break them; flaky e2e is usually bad waits or shared state, not bad luck
Nobody's grading you on drawing the triangle. They're grading you on knowing that a test which mocks the thing it's checking is worse than no test, because it's green for the wrong reason.




Comments
No comments yet — be the first to share your thoughts.