SigNoz uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (ClickHouse, PostgreSQL, etc.) to ensure end-to-end functionality.
Before running integration tests, ensure you have the following installed:
- Python 3.13+
- uv
- Docker (for containerized services)
- Navigate to the integration tests directory:
cd tests/integration- Install dependencies using uv:
uv syncNOTE: the build backend could throw an error while installing
psycopg2, pleae see https://www.psycopg.org/docs/install.html#build-prerequisites
To spin up all the containers necessary for writing integration tests and keep them running:
uv run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/setup.py::test_setupThis command will:
- Start all required services (ClickHouse, PostgreSQL, Zookeeper, etc.)
- Keep containers running due to the
--reuseflag - Verify that the setup is working correctly
When you're done writing integration tests, clean up the environment:
uv run pytest --basetemp=./tmp/ -vv --teardown -s src/bootstrap/setup.py::test_teardownThis will destroy the running integration test setup and clean up resources.
Python and pytest form the foundation of the integration testing framework. Testcontainers are used to spin up disposable integration environments. Wiremock is used to spin up test doubles of other services.
- Why Python/pytest? It's expressive, low-boilerplate, and has powerful fixture capabilities that make integration testing straightforward. Extensive libraries for HTTP requests, JSON handling, and data analysis (numpy) make it easier to test APIs and verify data
- Why testcontainers? They let us spin up isolated dependencies that match our production environment without complex setup.
- Why wiremock? Well maintained, documented and extensible.
.
├── conftest.py
├── fixtures
│ ├── __init__.py
│ ├── auth.py
│ ├── clickhouse.py
│ ├── fs.py
│ ├── http.py
│ ├── migrator.py
│ ├── network.py
│ ├── postgres.py
│ ├── signoz.py
│ ├── sql.py
│ ├── sqlite.py
│ ├── types.py
│ └── zookeeper.py
├── uv.lock
├── pyproject.toml
└── src
└── bootstrap
├── __init__.py
├── 01_database.py
├── 02_register.py
└── 03_license.py
Each test suite follows some important principles:
- Organization: Test suites live under
src/in self-contained packages. Fixtures (a pytest concept) live insidefixtures/. - Execution Order: Files are prefixed with two-digit numbers (
01_,02_,03_) to ensure sequential execution. - Time Constraints: Each suite should complete in under 10 minutes (setup takes ~4 mins).
Test suites should target functional domains or subsystems within SigNoz. When designing a test suite, consider these principles:
- Functional Cohesion: Group tests around a specific capability or service boundary
- Data Flow: Follow the path of data through related components
- Change Patterns: Components frequently modified together should be tested together
The exact boundaries for modules are intentionally flexible, allowing teams to define logical groupings based on their specific context and knowledge of the system.
Eg: The bootstrap integration test suite validates core system functionality:
- Database initialization
- Version check
Other test suites can be pipelines, auth, querier.
Now start writing an integration test. Create a new file src/bootstrap/05_version.py and paste the following:
import requests
from fixtures import types
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def test_version(signoz: types.SigNoz) -> None:
response = requests.get(signoz.self.host_config.get("/api/v1/version"), timeout=2)
logger.info(response)We have written a simple test which calls the version endpoint of the container in step 1. In order to just run this function, run the following command:
uv run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/05_version.py::test_versionNote: The
--reuseflag is used to reuse the environment if it is already running. Always use this flag when writing and running integration tests. If you don't use this flag, the environment will be destroyed and recreated every time you run the test.
Here's another example of how to write a more comprehensive integration test:
from http import HTTPStatus
import requests
from fixtures import types
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def test_user_registration(signoz: types.SigNoz) -> None:
"""Test user registration functionality."""
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/register"),
json={
"name": "testuser",
"orgId": "",
"orgName": "test.org",
"email": "test@example.com",
"password": "password123Z$",
},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
assert response.json()["setupCompleted"] is Trueuv run pytest --basetemp=./tmp/ -vv --reuse src/uv run pytest --basetemp=./tmp/ -vv --reuse src/<suite>
# Run querier tests
uv run pytest --basetemp=./tmp/ -vv --reuse src/querier/
# Run auth tests
uv run pytest --basetemp=./tmp/ -vv --reuse src/auth/uv run pytest --basetemp=./tmp/ -vv --reuse src/<suite>/<file>.py::test_name
# Run test_register in file 01_register.py in passwordauthn suite
uv run pytest --basetemp=./tmp/ -vv --reuse src/passwordauthn/01_register.py::test_registerTests can be configured using pytest options:
--sqlstore-provider- Choose database provider (default: postgres)--postgres-version- PostgreSQL version (default: 15)--clickhouse-version- ClickHouse version (default: 25.5.6)--zookeeper-version- Zookeeper version (default: 3.7.1)
Example:
uv run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postgres-version=14 src/auth/- Always use the
--reuseflag when setting up the environment to keep containers running - Use the
--teardownflag when cleaning up to avoid resource leaks - Follow the naming convention with two-digit numeric prefixes (
01_,02_) for test execution order - Use proper timeouts in HTTP requests to avoid hanging tests
- Clean up test data between tests to avoid interference
- Use descriptive test names that clearly indicate what is being tested
- Leverage fixtures for common setup and authentication
- Test both success and failure scenarios to ensure robust functionality