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

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 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. 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 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, the team demonstrates how to test asynchronous logic using Effect-TS:

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:

// 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 file for shared fixtures.

Database Testing Patterns

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


# 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 to reduce duplication. The following pattern demonstrates using the db fixture, a custom issue fixture, and the built-in client fixture:


# 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.

For the frontend, execute:

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:

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.
  • 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 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 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. Place backend test files using the test_*.py pattern inside apps/api/plane/tests/, following the existing conftest.py structure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →