TracksSpecializations and Deep DivesTesting StrategiesIntegration Testing(3 of 7)

Integration Testing

Unit tests verify individual pieces work correctly. But software is made of many pieces working together, and bugs often hide in the connections. Integration tests verify that components interact correctly — that your API actually saves data to the database, that your service correctly calls another service.

What Integration Tests Cover

Integration tests exercise real interactions:

  • An API endpoint that queries a database
  • A service that calls another service
  • A UI component connected to a state management store
  • A function that reads from the file system

Unlike unit tests, integration tests don't mock everything. They use real databases (usually test instances), real file systems, and sometimes real network calls between your own services.

Example: Testing an API Endpoint

Here's an integration test that verifies user creation works end-to-end:

def test_create_user_endpoint(client, test_db):
    # Make a real HTTP request to the API
    response = client.post('/users', json={
        'email': 'test@example.com',
        'password': 'secure123'
    })
    
    # Verify the response
    assert response.status_code == 201
    assert response.json()['email'] == 'test@example.com'
    
    # Verify the database was actually updated
    user = test_db.query(User).filter_by(
        email='test@example.com'
    ).first()
    assert user is not None
    assert user.password != 'secure123'  # Should be hashed

This test verifies the entire flow: HTTP handling, validation, database insertion, and password hashing. A unit test might mock the database and miss bugs in the actual SQL queries.

Managing Test Databases

Integration tests need databases, but you don't want to pollute your development database or slow down tests with network calls to a remote server.

Use a separate test database. Configure your tests to connect to a dedicated test instance.

Reset between tests. Each test should start with a clean slate. Use transactions that roll back, or truncate tables between tests.

Consider in-memory databases. SQLite in-memory mode is fast and requires no setup. Just ensure your production database's SQL dialect is compatible.

Balancing Coverage and Speed

Integration tests are slower than unit tests — they involve real I/O operations. You can't run thousands of them quickly.

The testing pyramid suggests: many unit tests, fewer integration tests, even fewer end-to-end tests. Use integration tests for critical paths and component boundaries, not for every edge case.

See More

Further Reading

Last updated December 26, 2025

You need to be signed in to leave a comment and join the discussion