# Pytest Testing Conventions and Fixtures in the Plane Django API: A Complete Guide

> Explore pytest testing conventions and fixtures used in the Plane Django API for robust database isolation, authenticated clients, and service mocking across test layers.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: testing
- Published: 2026-06-23

---

**The Plane Django API uses pytest-django with session-scoped fixtures defined in [`conftest.py`](https://github.com/makeplane/plane/blob/main/conftest.py) to provide database isolation, authenticated API clients, and mocked external services across unit, contract, and integration test layers.**

The open-source Plane project (makeplane/plane) implements a robust testing strategy for its Django backend using pytest. Understanding these pytest testing conventions and fixtures is essential for contributors working on the API layer, as they ensure isolated, deterministic tests that run efficiently in Docker-based CI pipelines.

## Test Organization Structure

The Plane repository organizes tests into three distinct layers under `apps/api/plane/tests/`:

- **unit/** – Pure unit tests that avoid database interactions and external services.
- **contract/** – API contract tests that exercise HTTP endpoints using the Django test server.
- **integration/** – Higher-level integration tests covering complex workflows.

This separation ensures fast feedback from unit tests while maintaining confidence in API contracts through dedicated HTTP tests.

## Pytest Configuration and Test Runner

Configuration lives in [`apps/api/pytest.ini`](https://github.com/makeplane/plane/blob/main/apps/api/pytest.ini), which configures the Django settings module and enables pytest-django markers. The test runner executes through Docker Compose:

```bash
docker compose -f docker-compose-test.yml pytest

```

The [`pytest.ini`](https://github.com/makeplane/plane/blob/main/pytest.ini) file points to the test settings module and configures markers used throughout the suite.

## Core Fixtures in conftest.py

The [`apps/api/plane/tests/conftest.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/tests/conftest.py) file defines session-scoped and function-scoped fixtures that provide the testing infrastructure.

### Database and Live Server Setup

The `django_db_setup` fixture (imported from `pytest_django.fixtures`) initializes the test database once per session. The `live_server` fixture runs a transient Django server on a random port for contract tests:

```python

# apps/api/plane/tests/conftest.py

import pytest
from rest_framework.test import APIClient

@pytest.fixture(scope="session")
def django_db_setup():
    """Initialize the Django test DB (executed once per session)."""
    # pytest-django handles DB creation automatically.

    pass

@pytest.fixture
def api_client(live_server):
    """Return an APIClient that talks to the live Django test server."""
    client = APIClient()
    client.defaults["SERVER_NAME"] = live_server.host
    client.defaults["SERVER_PORT"] = live_server.port
    return client

```

### API Client and Authentication Fixtures

Authentication fixtures generate JWT tokens and provide ready-to-use headers:

```python

# apps/api/plane/tests/conftest.py

from plane.tests.factories import UserFactory

@pytest.fixture
def create_user():
    """Create and return a regular user."""
    def _create(**kwargs):
        return UserFactory(**kwargs)
    return _create

@pytest.fixture
def admin_user():
    """Create a super-user."""
    return UserFactory(is_staff=True, is_superuser=True)

@pytest.fixture
def auth_headers(create_user):
    """Headers containing a JWT for a freshly created user."""
    user = create_user()
    # Token generation lives in plane.utils.auth

    token = user.get_jwt()
    return {"Authorization": f"Bearer {token}"}

```

### Factory-Based Model Fixtures

Plane uses **factory-boy** for model factories. The `UserFactory` and `ProjectFactory` create persisted model instances without manual boilerplate:

```python

# apps/api/plane/tests/conftest.py

from plane.tests.factories import UserFactory, ProjectFactory

@pytest.fixture
def user_factory():
    """Expose UserFactory as a fixture."""
    return UserFactory

@pytest.fixture
def project_factory():
    """Expose ProjectFactory as a fixture."""
    return ProjectFactory

```

## Mocking External Services

External dependencies are isolated in [`apps/api/plane/tests/conftest_external.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/tests/conftest_external.py). These fixtures patch Redis, Elasticsearch, Celery, and S3 with `MagicMock` objects:

```python

# apps/api/plane/tests/conftest_external.py

import pytest
from unittest.mock import MagicMock

@pytest.fixture
def label_service_mock():
    """Mock the label service to avoid external calls."""
    mock = MagicMock()
    mock.create.return_value = {"id": "lbl_1", "name": "Bug"}
    return mock

```

Tests import these mocks to verify interactions without network overhead.

## Writing Contract Tests with Fixtures

Contract tests combine the `api_client` and `auth_headers` fixtures to verify API behavior. In [`apps/api/plane/tests/contract/api/test_projects.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/tests/contract/api/test_projects.py):

```python
def test_create_project(api_client, auth_headers):
    payload = {"name": "New Project", "description": "Demo"}
    
    response = api_client.post(
        "/api/projects/",
        data=payload,
        format="json",
        **auth_headers,
    )
    
    assert response.status_code == 201
    assert response.data["name"] == "New Project"

```

The `api_client` provides a live server-aware HTTP client, while `auth_headers` injects a valid JWT. The database transaction rolls back automatically after the test completes.

### Parametrized Test Patterns

Plane uses `@pytest.mark.parametrize` for testing multiple scenarios without duplicate code:

```python
@pytest.mark.parametrize(
    "status,expected", [("open", 200), ("archived", 403)]
)
def test_project_access(api_client, auth_headers, create_user, status, expected):
    user = create_user()
    proj = ProjectFactory(owner=user, is_archived=(status == "archived"))
    resp = api_client.get(f"/api/projects/{proj.id}/", **auth_headers)
    assert resp.status_code == expected

```

### Mocking Example in Label Tests

When testing services that interact with external storage, use the mock fixtures:

```python

# apps/api/plane/tests/contract/api/test_labels.py

def test_create_label(label_service_mock, api_client, auth_headers):
    label_service_mock.create.return_value = {"id": "lbl_1", "name": "Bug"}
    
    payload = {"name": "Bug", "color": "#ff0000"}
    resp = api_client.post("/api/labels/", payload, **auth_headers)
    
    assert resp.status_code == 201
    label_service_mock.create.assert_called_once_with(payload)

```

## Summary

- **Test organization** splits tests into `unit/`, `contract/`, and `integration/` directories under `apps/api/plane/tests/`.
- **Session-scoped fixtures** in [`conftest.py`](https://github.com/makeplane/plane/blob/main/conftest.py) provide `django_db_setup`, `live_server`, and `api_client` for efficient test isolation.
- **Authentication fixtures** like `auth_headers` and `create_user` handle JWT generation and user creation via factory-boy.
- **External mocks** in [`conftest_external.py`](https://github.com/makeplane/plane/blob/main/conftest_external.py) prevent tests from hitting Redis, Elasticsearch, Celery, or S3.
- **Contract tests** use the `api_client` fixture to exercise live HTTP endpoints with automatic database rollback.

## Frequently Asked Questions

### How does Plane handle database isolation in pytest?

Plane relies on pytest-django's transactional fixtures. The `django_db_setup` fixture creates the test database once per session, while individual tests use the `db` fixture (or fixtures that depend on it) to wrap each test in a transaction that rolls back after completion. This ensures test isolation without recreating the database for every test.

### What is the difference between unit and contract tests in Plane?

Unit tests in `apps/api/plane/tests/unit/` verify individual model methods or utility functions without database access, as seen in [`apps/api/plane/tests/unit/models/test_issue_comment_modal.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/tests/unit/models/test_issue_comment_modal.py). Contract tests in `apps/api/plane/tests/contract/api/` use the `api_client` and `live_server` fixtures to make actual HTTP requests against the Django application, verifying that API endpoints return the expected status codes and response structures.

### How do I authenticate API requests in Plane's pytest suite?

Import the `auth_headers` fixture into your test function. This fixture creates a user via `create_user` (which uses `UserFactory`), generates a JWT token using the `get_jwt()` method from `plane.utils.auth`, and returns a dictionary with the `Authorization: Bearer <token>` header. Pass this to `api_client` requests using the unpacking operator `**auth_headers`.

### Where are external services like Redis and Celery mocked?

External service mocks reside in [`apps/api/plane/tests/conftest_external.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/tests/conftest_external.py). These fixtures use `unittest.mock.MagicMock` to patch service clients before they are imported by the application code, ensuring that tests never attempt real network connections to Redis, Elasticsearch, Celery workers, or S3 storage.