-
Notifications
You must be signed in to change notification settings - Fork 7
Creating your first tests
Summary: Learn how to test your application, starting from the simplest requests to dealing with security tokens and understanding why StartER uses an advanced "contract" system.
StartER uses an advanced Contract-driven testing architecture (located in tests/contracts.ts) to automatically verify the core framework's health. While this is powerful, it is abstract and not recommended for beginners.
When building your own features, the best way to learn is by writing standard, "linear" tests using vitest, supertest, and testing-library. Let's explore how tests evolve from simple to complex.
The easiest way to test your API is with a GET request, as it doesn't modify data and doesn't require security tokens.
Create a file right next to your repository, for example src/express/modules/group/group.test.ts:
import { describe, it, expect } from "vitest";
import request from "supertest";
// Import the main Express application
import app from "../../../../app";
describe("Group API", () => {
it("should fetch all groups successfully", async () => {
// 1. Arrange & Act: Send a GET request
const response = await request(app).get("/api/groups");
// 2. Assert: Verify the status code and body
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
});
});Tip
Where to put your tests?
-
Colocation: keep
group.test.tsnext togroupActions.tsfor perfect context. -
Dedicated folder: move all tests to a central
tests/directory to keep your source folders clean. Both are valid industry practices! StartER lets you choose what fits your workflow best.
When you try to test a POST, PUT, or DELETE request, you will likely encounter a 403 Forbidden error. Why?
StartER enforces double-submit cookie CSRF protection on all mutative routes to prevent malicious attacks. To test a POST request, you must manually simulate this protection by sending a matching cookie and header.
it("should create a group successfully", async () => {
// 1. Arrange: Create a fake CSRF token
const fakeCsrfToken = "test-csrf-token";
// 2. Act: Send the POST request with the token in both the cookie and the header
const response = await request(app)
.post("/api/groups")
.set("Cookie", [`__Host-x-csrf-token=${fakeCsrfToken}`])
.set("X-CSRF-Token", fakeCsrfToken)
.send({ name: "My Test Group" });
// 3. Assert
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("insertId");
});Testing React components involves rendering the component and simulating user interactions. For components that depend on routing (like links or navigation), you should use a Route Stub.
Create your test file next to your component, for example src/react/components/group/GroupCreate.test.tsx:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { createRoutesStub } from "react-router";
import { vi } from "vitest";
import GroupCreate from "./GroupCreate";
describe("GroupCreate Component", () => {
it("should submit the form correctly", async () => {
// 1. Arrange: Manually mock the browser's fetch API
const fetchMock = vi.spyOn(global, "fetch").mockResolvedValue({
status: 201,
json: async () => ({ insertId: 1 }),
} as Response);
const user = userEvent.setup();
// 2. Act: Create a route stub for the component
const Stub = createRoutesStub([
{ path: "/groups/new", Component: GroupCreate }
]);
render(<Stub initialEntries={["/groups/new"]} />);
// 3. Act: Simulate user interaction
await user.type(screen.getByLabelText(/name/i), "My Test Group");
await user.click(screen.getByRole("button", { name: /submit/i }));
// 4. Assert: Verify the fetch call
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining("/api/groups"),
expect.objectContaining({ method: "POST" })
);
});
});Now, imagine your POST /api/groups route also requires the user to be authenticated.
To test this manually, you would need to:
- Generate a mock JWT token using
jsonwebtoken. - Mock the database to ensure the user exists.
- Pass the
__Host-auth=jwtcookie. - Pass the
__Host-x-csrf-token=abcdcookie. - Pass the
X-CSRF-Token: abcdheader.
The same applies to React: as your components make more API calls, mocking fetch for every single test becomes a massive burden.
This is exactly why the contract system exists.
The contract system abstracts away the repetitive boilerplate of setting up auth, CSRF, and database states. It allows you to define the behavior once in a central place and use it to verify both your backend and your frontend automatically.
Vitest is configured to automatically find any file ending in .test.ts or .test.tsx. To execute your tests, run:
npm run testAI co-creation
Getting started
Explanations
How-To Guides
Reference
Digging deeper