Introduction
Unit tests verify one component at a time, but real applications are made of many components working together — services, databases, message queues, and external APIs. Integration testing and system testing exist to catch the bugs that only appear when those pieces interact.
Cricket analogy: Testing a bowler's individual delivery accuracy in the nets is like a unit test, but it can't reveal how they'll perform against a real batting partnership under match pressure with fielders and a scoreboard in play.
Explanation
Integration testing verifies that two or more components work correctly together, such as a service layer talking to a real (or test) database, or two microservices communicating over HTTP. Unlike a unit test, an integration test does not isolate the component from its collaborators; instead it deliberately exercises the boundary between them, catching problems like incorrect SQL queries, serialization mismatches, or misconfigured API contracts that unit tests — which typically mock those collaborators — would miss.
Cricket analogy: Checking that a bowler's yorker actually gets a batsman out in a real practice match against the middle order is an integration test, deliberately exercising the bowler-batsman boundary rather than isolating the bowler's technique alone.
System testing goes a level further: it validates the entire application as a complete, integrated whole, typically through the same interfaces a real user or external client would use, such as HTTP endpoints or a browser UI. System tests (sometimes called end-to-end tests) exercise the full deployed stack — frontend, backend, database, and any third-party integrations — to confirm that the system as a whole meets its requirements. To summarize the three levels: unit testing isolates a single component with all dependencies replaced or removed; integration testing checks a small group of real components interacting; system testing checks the entire application end-to-end.
Cricket analogy: A full day-night pink-ball Test match, played start to finish with real umpires, real crowds, and real broadcast feeds, is system testing: unit tests check a single delivery, integration tests check bowler-batsman interaction, and the full match checks everything end to end.
Example
# Integration test: the OrderService talking to a real test database.
# Unlike a unit test, we do NOT mock the database here — we want to
# verify the actual SQL and data mapping work correctly together.
import pytest
def test_order_service_persists_order_to_database(test_db_session):
# Arrange: a real (test) database session is provided by a fixture
service = OrderService(db_session=test_db_session)
# Act
order_id = service.create_order(customer_id=42, items=["sku-1", "sku-2"])
# Assert: read back from the same real database to confirm persistence
saved_order = test_db_session.query(Order).get(order_id)
assert saved_order.customer_id == 42
assert len(saved_order.items) == 2
# System test: exercising the whole application through its public HTTP API,
# the same way a real client would.
def test_checkout_flow_end_to_end(http_client):
# Arrange
payload = {"customer_id": 42, "items": ["sku-1", "sku-2"]}
# Act
response = http_client.post("/api/orders", json=payload)
# Assert
assert response.status_code == 201
assert response.json()["status"] == "confirmed"Analysis
The integration test uses a real test database session rather than a mock, so it can catch bugs that only appear when the ORM mapping and actual SQL execute together — something a unit test with a mocked database would never detect. The system test goes further still: it sends an HTTP request to the running application's public API and checks the full response, exercising routing, validation, business logic, and persistence together, just as a real client would experience it. Notice the tradeoff: the system test tells you the least about where a failure occurred (it could be anywhere in the stack), but it gives the strongest guarantee that the feature actually works for real users, which is why the pyramid keeps these tests few in number and reserves them for critical user journeys.
Cricket analogy: A real intra-squad match with real fielders catches issues a solo net session with a bowling machine never would, but a full televised international match reveals the truest test of form under real pressure, which is why boards schedule few such series and reserve them for crucial fixtures.
Key Takeaways
- Unit testing isolates a single component and replaces its dependencies.
- Integration testing verifies real interaction between two or more components, such as a service and a database.
- System testing validates the entire deployed application end-to-end through its real interfaces.
- Integration and system tests catch bugs at component boundaries that unit tests, which mock collaborators, cannot.
- System tests give the strongest confidence but are the slowest and hardest to debug when they fail.
Practice what you learned
1. What distinguishes integration testing from unit testing?
2. System testing is best described as:
3. In the integration test example, why is a real test database used instead of a mock?
4. Which statement correctly orders the scope of the three testing levels from narrowest to broadest?
Was this page helpful?
You May Also Like
Software Testing Fundamentals
An overview of why we test software, the testing pyramid, and the difference between black-box and white-box testing.
Unit Testing
Learn how to write isolated, fast unit tests for pure functions using the Arrange-Act-Assert pattern.
Mocking and Test Doubles
Learn the five canonical test double types—dummy, stub, spy, mock, and fake—and how to use unittest.mock in Python.
Monolith vs Microservices
Compares monolithic and microservices architectures across deployment, scaling, communication, complexity, and failure isolation.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics