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.tspattern placed next to source files, while backend code usestest_*.pyfiles insideapps/api/plane/tests/. - Run the linter and formatter before committing. The repository uses OxLint and oxfmt for TypeScript; execute
pnpm check:lintandpnpm check:formatto 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
withTimeoutAndRetryutility 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
describeblocks with short, descriptive titles. - Await all async operations since Effect runs return Promises.
- Assert failure paths by wrapping effects with
Effect.eitherand checkingEither.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.tsfiles for frontend changes andtest_*.pyfiles for backend changes. - Use Vitest for TypeScript/React code in
apps/web/andapps/live/, leveragingdescribe/itblocks and Effect-TS patterns where applicable. - Use pytest for Django code in
apps/api/, marking database tests with@pytest.mark.django_dband utilizing fixtures fromconftest.py. - Mock external dependencies using MSW for HTTP requests in frontend tests to maintain unit test purity.
- Run
pnpm check:lintandpnpm check:formatbefore committing to ensure tests meet the repository's style requirements. - Execute backend tests via Docker using
docker-compose-test.ymlto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →