HTTP Test Server Fixture#
The HTTP test server fixture provides a way to test conda functionality that requires serving files over HTTP, such as:
Mock conda channels with packages
Remote environment files (
environment.yml)Remote configuration files
Any scenario where conda needs to fetch files from a URL
Overview#
The http_test_server fixture starts a local HTTP server that serves files from a directory. The server runs on a random port and supports both IPv4 and IPv6.
The fixture can be used in two ways:
Without
@pytest.mark.parametrize- Use a temporary directory that you populate dynamicallyWith
@pytest.mark.parametrize- Serve files from a pre-existing directory
Tip
For proper type hints, import HttpTestServerFixture from conda.testing.fixtures under TYPE_CHECKING.
See the complete example for the full import pattern.
Basic Usage#
Dynamic Content (No Marker)#
The simplest usage - no marker needed. The server automatically uses a temporary directory that you can populate:
def test_dynamic_repodata(http_test_server: HttpTestServerFixture):
"""Create content on the fly - no setup needed."""
# Populate files directly in the server's directory
(http_test_server.directory / "repodata.json").write_text('{"packages": {}}')
# Make request
response = requests.get(http_test_server.get_url("repodata.json"))
assert response.status_code == 200
assert response.json() == {"packages": {}}
This pattern is ideal for:
Creating mock repodata files
Testing with minimal setup
Extending and creating your own fixtures programmatically
Pre-existing Directory (With Parametrize)#
Use @pytest.mark.parametrize() with indirect=True when you have test data already prepared:
@pytest.mark.parametrize(
"http_test_server",
["tests/data/mock-channel"],
indirect=True,
)
def test_fetch_from_channel(http_test_server: HttpTestServerFixture):
# Server serves files from tests/data/mock-channel/
repodata_url = http_test_server.get_url("linux-64/repodata.json")
response = requests.get(repodata_url)
assert response.status_code == 200
The indirect=True parameter tells pytest to pass the directory path to the fixture rather than directly to the test function.
This pattern is ideal for:
Complex directory structures
Sharing test data across multiple tests
Binary files (packages, archives)
Large test datasets
Testing Multiple Directories#
One of the benefits of using @pytest.mark.parametrize is that you can easily test the same logic against multiple directories:
@pytest.mark.parametrize(
"http_test_server",
[
"tests/data/channel1",
"tests/data/channel2",
"tests/data/channel3",
],
indirect=True,
)
def test_multiple_channels(http_test_server: HttpTestServerFixture):
# This test runs three times, once for each channel directory
response = requests.get(http_test_server.get_url("repodata.json"))
assert response.status_code == 200
assert "packages" in response.json()
Each test run will use a different directory, making it easy to verify behavior across multiple datasets.
You can also mix pre-existing directories with dynamic content by using None:
@pytest.mark.parametrize(
"http_test_server",
[
"tests/data/channel1",
None,
"tests/data/channel2",
],
indirect=True,
)
def test_mixed_sources(http_test_server: HttpTestServerFixture):
# Runs 3 times: channel1, dynamic tmp dir, channel2
# When None, http_test_server.directory is a fresh temporary directory
...
Complete Example: Testing a Mock Channel#
Here’s a full example with all imports showing dynamic content generation:
from __future__ import annotations
import json
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
import requests
if TYPE_CHECKING:
from conda.testing.fixtures import CondaCLIFixture, HttpTestServerFixture
def test_install_from_mock_channel(
http_test_server: HttpTestServerFixture,
conda_cli: CondaCLIFixture,
tmp_path: Path,
):
"""Test installing from a dynamically created mock channel."""
# Create channel structure on the fly
noarch = http_test_server.directory / "noarch"
noarch.mkdir()
# Create minimal repodata
repodata = {"packages": {}, "packages.conda": {}, "repodata_version": 1}
(noarch / "repodata.json").write_text(json.dumps(repodata))
# Use the channel
channel_url = http_test_server.url
stdout, stderr, code = conda_cli(
"search",
f"--channel={channel_url}",
"--override-channels",
"*",
)
# Verify it worked (no packages found but channel was accessible)
assert code == 0
@pytest.mark.parametrize(
"http_test_server",
["tests/data/mock-channel"],
# Assume the following structure:
# tests/data/mock-channel/
# ├── noarch/
# │ └── repodata.json
# └── linux-64/
# ├── repodata.json
# └── example-pkg-1.0.0-0.tar.bz2
indirect=True,
)
def test_install_from_preexisting_channel(
http_test_server: HttpTestServerFixture,
conda_cli: CondaCLIFixture,
tmp_path: Path,
):
"""Test installing from pre-existing mock channel."""
channel_url = http_test_server.url
stdout, stderr, code = conda_cli(
"create",
f"--prefix={tmp_path}",
f"--channel={channel_url}",
"example-pkg",
"--yes",
)
assert code == 0
assert (tmp_path / "conda-meta" / "example-pkg-1.0.0-0.json").exists()
Fixture API Reference#
HttpTestServerFixture#
The fixture returns an instance with these attributes and methods:
Attributes:#
server: http.server.ThreadingHTTPServer- The underlying server instancehost: str- Server host (usually127.0.0.1)port: int- Server port (random)url: str- Base URL (e.g.,http://127.0.0.1:54321)directory: Path- The directory being served (writable, use to populate content)
Methods:#
get_url(path: str = "") -> str- Get full URL for a pathExample:
get_url("linux-64/repodata.json")→"http://127.0.0.1:54321/linux-64/repodata.json"
Using the directory attribute:#
def test_dynamic_files(http_test_server: HttpTestServerFixture):
# Write files directly to the served directory
(http_test_server.directory / "file.txt").write_text("content")
# Create subdirectories
subdir = http_test_server.directory / "subdir"
subdir.mkdir()
(subdir / "nested.json").write_text('{"key": "value"}')
# Files are immediately accessible via HTTP
response = requests.get(http_test_server.get_url("subdir/nested.json"))
assert response.json() == {"key": "value"}
Use in Downstream Projects#
The HTTP test server fixture is part of the conda.testing module and can be used by downstream projects:
# In your project's conftest.py
pytest_plugins = "conda.testing.fixtures"
Then use it in your tests:
@pytest.mark.parametrize("http_test_server", ["tests/my-mock-channel"], indirect=True)
def test_with_mock_channel(http_test_server: HttpTestServerFixture):
channel_url = http_test_server.url
# ... your test code ...
Troubleshooting#
“ValueError: Directory does not exist”#
This error occurs when using
@pytest.mark.parametrize()with an invalid pathCheck that the directory path provided in parametrize exists
Use absolute paths or paths relative to the repository root
Use
Path(__file__).parent / "data"if neededOr omit the parametrize decorator entirely to use a temporary directory
“ValueError: Path is not a directory”#
This error occurs when the parametrize value points to a file instead of a directory
Ensure the path in
@pytest.mark.parametrize(..., indirect=True)points to a directoryOr use the fixture without parametrize for dynamic content
Address already in use#
The fixture uses random ports, so this is rare
If it happens, the test will likely fail and retry automatically
Server not shutting down cleanly#
This is handled automatically by the fixture
The server runs on a daemon thread and will be cleaned up when tests finish
Files not appearing in HTTP responses#
Make sure files are written before making the HTTP request
Check that file paths don’t have leading slashes when using
get_url()Verify the directory structure with
list(http_test_server.directory.iterdir())
Tips and Best Practices#
Prefer dynamic content: Use the fixture without parametrize (dynamic content) for simple use cases. It’s simpler and doesn’t require maintaining test data files.
Use parametrize for complex data: Use
@pytest.mark.parametrize(..., indirect=True)when you have complex directory structures, binary files, or data shared across many tests.Function scope for isolation: Each test gets its own temporary directory with the
http_test_serverfixture (function scope), providing complete isolation.Organize test data: When using parametrize, keep mock channel data in dedicated directories like
tests/data/mock-channels/with README files explaining the structure.Test error scenarios: Use dynamic content to easily test edge cases like malformed repodata, missing packages, or network timeouts.
Cleanup is automatic: The fixture handles cleanup automatically - no need to manually shut down servers or delete temporary files.
Examples from conda Test Suite#
See these files for real-world usage examples:
tests/testing/test_http_test_server.py- Tests for the fixture itselftests/env/test_create.py::test_create_update_remote_env_file- Using remote environment filestests/gateways/test_connection.py- Connection and download testing