Unit testing vs integration testing: key differences explained
By qtrl Team · Engineering
Unit tests verify one piece of code in isolation. Integration tests verify that pieces work together. Both belong on every serious codebase, in different proportions for different products. The arguments online about which one matters more are mostly misunderstandings about what each one is for.
Plain-English definitions
A unit test exercises a single function, method, or class, with its dependencies stubbed or mocked. If the unit fails, the test fails; nothing else matters. They're fast (milliseconds), run on every commit, and form the wide base of the test pyramid.
An integration test exercises multiple pieces working together: a function calling a real database, a service talking to another service, an API hitting a real cache. The pieces are no longer isolated, so the tests are slower (seconds, sometimes minutes) and more expensive to run.
Side-by-side comparison
| Property | Unit testing | Integration testing |
|---|---|---|
| Scope | One function, class, or module | Multiple components, often with real dependencies |
| Speed | Milliseconds | Seconds to minutes |
| Run frequency | On every commit, locally and in CI | On every PR or merge to main |
| Setup cost | Low; mocks or stubs only | Higher; real database, broker, queue, etc. |
| Catches | Logic bugs in isolation | Wiring bugs, contract mismatches, integration drift |
| Misses | Integration-level issues | Subtle logic bugs that mocks would catch |
| Failure debug time | Seconds; the failure is localized | Minutes; the failure could be anywhere |
| Owner | Developer who wrote the code | Developer (sometimes shared with QA) |
How this shows up in modern teams
The healthy pattern on most products: lots of unit tests, a focused set of integration tests on the seams that matter, and a small set of end-to-end tests above them. The numbers depend on the product, but the shape is the classic practical test pyramid.
The mistake the pyramid was reacting against (the "ice-cream cone" anti-pattern) is still common: heavy E2E coverage, light integration, light unit. That shape is expensive to maintain, slow to run, and tells you less than the pyramid does about where the bug actually is.
A modern QA workflow example
A payment service in production. Unit tests cover the discount logic, tax calculation, currency conversion, with mocked payment-processor responses. Integration tests cover the path from the API handler through the database and the queue, against a real ephemeral database in CI. The handful of E2E tests above cover the full checkout flow through the UI.
When the discount logic regresses, a unit test fails in seconds and the developer knows exactly which function broke. When the payment-processor contract changes, an integration test catches it before a customer ever sees the error. When the checkout button stops responding because of a front-end change, the E2E test catches that. Three layers, three different kinds of bugs caught at different costs.
The common mistake: skipping integration tests
It's tempting to lean on heavy unit coverage and skip integration tests. Unit tests are easier to write, faster to run, and feel productive. The problem is that most production incidents aren't logic bugs inside a function. They're wiring bugs: a contract that changed, a configuration that drifted, a service that returns a slightly different shape than the caller expects.
Mocks can't catch those. Integration tests with real dependencies can. The cost is worth paying. Skipping integration tests is one of the most reliable ways to ship a regression that 100% unit-test coverage failed to flag.
When to write each one
Write a unit test when:
- The code has interesting logic worth pinning down.
- The function has multiple branches you want to verify independently.
- You want the failure to point directly at one function.
Write an integration test when:
- The code crosses a boundary (service, database, queue, third-party API).
- The interesting behavior involves how pieces compose, not the pieces themselves.
- A unit test would require so much mocking that it's not really testing the code anymore.
The mocking trap
Heavy mocking turns unit tests into tests of the mock, not the code. A unit test with five mocked dependencies usually passes whether the real code works or not. The rule of thumb: if you can't test a unit without mocking most of its world, the unit is probably the wrong size. Refactor or move the test up to integration.
What about end-to-end tests?
E2E tests are the layer above integration tests. They drive the full system through the user-facing surface (a browser, a CLI, an API). They catch the bugs that unit and integration tests can't see, but they're slow and expensive. The pyramid rule is to keep them few. For modern E2E framework choices, see Playwright vs Selenium and Playwright vs Cypress. Where agentic execution fits is in what is agentic testing.
Where qtrl fits
Unit and integration testing are developer concerns; the right tool is your language's test runner. qtrl is for the layers above: scripted E2E, agentic execution, manual cases, and the management layer that holds traceability across all of them. The whole stack matters, but a test management system like qtrl doesn't replace the unit-test pyramid below it. It pairs with it.
Frequently asked questions
Are unit tests more important than integration tests? Both matter. Unit tests give fast feedback on logic; integration tests catch wiring bugs nothing else sees. The pyramid is the wrong framing if you treat it as a hierarchy of importance.
What ratio of unit to integration tests should I aim for? It depends on the product, but more unit than integration is the usual target. A common starting point is something like 70% unit, 20% integration, 10% E2E, then adjust based on where production bugs actually come from.
Should developers write integration tests or QA? Usually developers, with QA reviewing the coverage. Integration tests need to live in the same repo as the code; QA owns the layers above.
What test runner should I use? Whatever your language's default is. JUnit for Java, pytest for Python, Vitest or Jest for JavaScript/TypeScript, NUnit or xUnit for .NET. The runner matters less than the discipline of running them.
Where does API testing fit? API tests are usually a flavor of integration test: real service, real dependencies, exercised through the public interface. Same rules apply.
The thing nobody emphasizes enough
Tests catch the bugs they're designed to catch. Unit tests catch logic regressions. Integration tests catch wiring drift. E2E tests catch flow breakage. Skip a layer and you ship the bugs that layer was supposed to catch. The teams that ship reliably aren't the ones with the highest unit coverage. They're the ones that pick the layers right and invest in each one. The pyramid is a useful model. The work of making it real is yours.
For the E2E and agentic layers that sit on top of your unit and integration tests, qtrl is one option. Try it out and see how it slots in.
Have more questions about AI testing and QA? Check out our FAQ