# Best Practices for Writing Tests in the Plane Codebase: A Complete Guide

> Master writing tests in the Plane codebase with our definitive guide. Learn Vitest for frontend and pytest for backend to ensure reliable code changes and successful merges.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: best-practices
- Published: 2026-06-18

---

**Every change in the Plane monorepo must include deterministic unit tests—using Vitest for the TypeScript/React frontend and pytest for the Django/Python backend—along with passing lint and format checks before merge.**

Plane is an open-source project management tool built as a monorepo that combines a TypeScript/React frontend and a Django/Python backend. Writing tests in this codebase requires following specific patterns established in [`CONTRIBUTING.md`](https://github.com/makeplane/plane/blob/main/CONTRIBUTING.md) and demonstrated throughout the `apps/` directory to ensure CI reliability and code quality.

## General Testing Requirements

All contributions to the Plane repository must adhere to the testing standards defined in [`CONTRIBUTING.md`](https://github.com/makeplane/plane/blob/main/CONTRIBUTING.md). The codebase enforces a strict policy that every new feature, bug fix, or refactor must be covered by automated tests.

Key requirements include:

- **Add a test file for every public API change**. Frontend code uses the `*.test.ts` pattern placed next to source files, while backend code uses `test_*.py` files inside `apps/api/plane/tests/`.
- **Run the linter and formatter before committing**. The repository uses OxLint and oxfmt for TypeScript; execute `pnpm check:lint` and `pnpm check:format` to verify syntax.
- **Use the `@/` alias for imports**. Import modules from `@/services/...` rather than relative paths to ensure consistent path resolution across the monorepo.
- **Write deterministic tests**. Avoid timing-sensitive code; use controlled timeout values as demonstrated in the `withTimeoutAndRetry` utility tests.

## Frontend Testing with Vitest

The Plane frontend uses **Vitest** as its test runner, configured in [`pnpm-workspace.yaml`](https://github.com/makeplane/plane/blob/main/pnpm-workspace.yaml) and orchestrated via Turbo. Tests reside alongside source code and follow the `*.test.ts` naming convention.

### Testing Effect-TS Utilities

In [`apps/live/tests/services/pdf-export/effect-utils.test.ts`](https://github.com/makeplane/plane/blob/main/apps/live/tests/services/pdf-export/effect-utils.test.ts), the team demonstrates how to test asynchronous logic using Effect-TS:

```typescript
import { describe, it, expect, assert } from "vitest";
import { Effect, Either } from "effect";
import { withTimeoutAndRetry } from "@/services/pdf-export/effect-utils";
import { PdfTimeoutError } from "@/schema/pdf-export";

describe("effect-utils", () => {
  it("should succeed when effect completes within timeout", async () => {
    const effect = Effect.succeed("success");
    const wrapped = withTimeoutAndRetry("test-operation")(effect);
    const result = await Effect.runPromise(wrapped);
    expect(result).toBe("success");
  });

  it("should retry on timeout and eventually fail", async () => {
    const failingEffect = Effect.fail(new PdfTimeoutError());
    const wrapped = withTimeoutAndRetry("test-operation", { retries: 2 })(failingEffect);
    const result = await Effect.runPromise(Effect.either(wrapped));
    
    assert(Either.isLeft(result), "Expected Left after exhausted retries");
  });
});

```

Key practices from this implementation include:

- **Group related scenarios** using `describe` blocks with short, descriptive titles.
- **Await all async operations** since Effect runs return Promises.
- **Assert failure paths** by wrapping effects with `Effect.either` and checking `Either.isLeft`.
- **Provide custom assertion messages** via `assert(condition, "message")` to improve failure diagnostics.

### Mocking HTTP Requests with MSW

For API testing, Plane uses Mock Service Worker (MSW) to intercept network calls without external dependencies:

```typescript
// Example pattern for testing fetch utilities
import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
import { fetchJson } from "@/services/api/fetcher";
import { server, rest } from "msw";
import { setupServer } from "msw/node";

const mswServer = setupServer(
  rest.get("/api/data", (req, res, ctx) => res(ctx.json({ ok: true })))
);

beforeAll(() => mswServer.listen());
afterAll(() => mswServer.close());
afterEach(() => mswServer.resetHandlers());

describe("fetchJson", () => {
  it("returns parsed JSON on 200", async () => {
    const data = await fetchJson("/api/data");
    expect(data).toEqual({ ok: true });
  });

  it("throws on non-2xx responses", async () => {
    mswServer.use(
      rest.get("/api/data", (req, res, ctx) => res(ctx.status(500)))
    );
    await expect(fetchJson("/api/data")).rejects.toThrow("Request failed");
  });
});

```

## Backend Testing with pytest

The Django backend in `apps/api/` uses **pytest** with Docker-based test execution. The standard layout places tests under `apps/api/plane/tests/` with a [`conftest.py`](https://github.com/makeplane/plane/blob/main/conftest.py) file for shared fixtures.

### Database Testing Patterns

Mark any test touching the database with `@pytest.mark.django_db` to ensure proper transaction isolation:

```python

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

import pytest
from plane.models import Issue

@pytest.mark.django_db
def test_issue_str():
    issue = Issue(title="Fix bug")
    assert str(issue) == "Fix bug"

```

### Using Fixtures for Setup

Centralize test setup in [`conftest.py`](https://github.com/makeplane/plane/blob/main/conftest.py) to reduce duplication. The following pattern demonstrates using the `db` fixture, a custom `issue` fixture, and the built-in `client` fixture:

```python

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

import pytest
from django.urls import reverse

@pytest.fixture
def issue(db):
    from plane.models import Issue
    return Issue.objects.create(title="Test issue")

def test_issue_detail(client, issue):
    url = reverse("issue-detail", args=[issue.id])
    resp = client.get(url)
    assert resp.status_code == 200
    assert resp.json()["title"] == "Test issue"

```

## Running Tests in the Monorepo

Plane orchestrates test execution through **Turbo**, defined in [`turbo.json`](https://github.com/makeplane/plane/blob/main/turbo.json).

For the frontend, execute:

```bash
pnpm run test

```

This command wraps `vitest run` across the UI packages and enforces the workspace linting rules.

For the backend, use the Docker Compose test stack defined in [`docker-compose-test.yml`](https://github.com/makeplane/plane/blob/main/docker-compose-test.yml):

```bash
docker compose -f docker-compose-test.yml up api-tests

```

This runs `pytest` inside a container matching the production Python and Django versions, ensuring environment consistency.

## Summary

- **Every change requires tests**—add `*.test.ts` files for frontend changes and `test_*.py` files for backend changes.
- **Use Vitest** for TypeScript/React code in `apps/web/` and `apps/live/`, leveraging `describe`/`it` blocks and Effect-TS patterns where applicable.
- **Use pytest** for Django code in `apps/api/`, marking database tests with `@pytest.mark.django_db` and utilizing fixtures from [`conftest.py`](https://github.com/makeplane/plane/blob/main/conftest.py).
- **Mock external dependencies** using MSW for HTTP requests in frontend tests to maintain unit test purity.
- **Run `pnpm check:lint` and `pnpm check:format`** before committing to ensure tests meet the repository's style requirements.
- **Execute backend tests via Docker** using [`docker-compose-test.yml`](https://github.com/makeplane/plane/blob/main/docker-compose-test.yml) to match production environments.

## Frequently Asked Questions

### What testing frameworks does Plane use?

Plane uses **Vitest** for the TypeScript/React frontend and **pytest** for the Django/Python backend. The frontend also utilizes **MSW (Mock Service Worker)** for HTTP mocking and **Effect-TS** for handling asynchronous operations in tests located in `apps/live/tests/`.

### How do I mock HTTP requests in Plane frontend tests?

Use **MSW** (`msw` and `msw/node`) to intercept network calls. Set up the server in `beforeAll`, clean up in `afterAll`, and reset handlers in `afterEach` to ensure test isolation, as demonstrated in the fetch utility test patterns.

### Do I need to run tests inside Docker for the backend?

Yes. The backend tests must run inside the Docker environment defined in [`docker-compose-test.yml`](https://github.com/makeplane/plane/blob/main/docker-compose-test.yml) to ensure the correct Python version, Django settings, and database dependencies match the production configuration.

### Where should I place new test files in the Plane repository?

Place frontend test files using the `*.test.ts` pattern adjacent to the source code they test, such as [`apps/live/tests/services/pdf-export/effect-utils.test.ts`](https://github.com/makeplane/plane/blob/main/apps/live/tests/services/pdf-export/effect-utils.test.ts). Place backend test files using the `test_*.py` pattern inside `apps/api/plane/tests/`, following the existing [`conftest.py`](https://github.com/makeplane/plane/blob/main/conftest.py) structure.