Testing en US - rocambille/start-express-react GitHub Wiki

This section describes the organization, objectives, and best practices for testing the framework. The approach aims to provide a reproducible, fast, and isolated environment for testing both the Express API and React components.

The entire setup is based on Vitest, supertest, and the React Testing Library.

Objectives

Testing serves several purposes:

  • Verify that the project installation is working (necessary files, configuration, database connection).
  • Test the API's behavior in isolation, without a real database.
  • Test React components through controlled rendering.
  • Ensure that business rules (authorization, validation, consistency) are respected.
  • Provide a stable, reproducible environment compatible with CI/CD pipelines.

The goal is not to achieve maximum coverage, but to cover the critical points: authentication, resource access, input validation, and state consistency.

Test organization

Tests are grouped in the tests/ folder according to three categories:

  • tests/setup/: installation and environment tests.
  • tests/api/: Express API integration tests.
  • tests/react/: React component tests.

Each group uses its own tools and mocking strategies, while maintaining a common philosophy: executing the complete business logic while controlling the external environment (database, network calls, authentication).

Installation tests

The tests in the tests/setup/ folder validate the proper functioning of the minimal environment:

  • existence and content of the .env file
  • correct initialization of the MySQL client
  • presence of the tables defined in schema.sql

These tests are useful for detecting common errors during the initial project launch or during automated deployment.

For example:

describe("Database connection", () => {
  it("should connect successfully", async () => {
    const connection = await databaseClient.getConnection();

    expect(connection).toBeDefined();

    await connection.release();
  });
});

API tests

API tests are organized by module (auth, items...) and use a full Express environment, but with a completely mocked database.

Mock database

The goal is to test the API without relying on a MySQL instance. The tests/api/utils.ts file provides a mockDatabaseClient() function that replaces databaseClient.query with an in-memory implementation capable of responding to common SQL queries (SELECT, INSERT, UPDATE, DELETE).

The initial dataset (mockedData) is reset for each test using resetMockData() to ensure isolation and reproducibility.

See the full code for details:

Mock authentication

JWT calls are mocked at the test level to precisely control the execution context:

vi.spyOn(jwt, "verify").mockImplementation(
  () => ({ sub: "1" })
);

This allows you to test authorization rules. For example: a user can only modify their own resources.

HTTP calls with supertest

API tests use a real Express server set up in an isolated environment with supertest:

const app = express();

app.use(routes);

const api = supertest(app);

const response = await api.get("/api");

Actions, validators, and middleware are therefore tested in an integrated manner, while remaining independent of an external database.

React Tests

React tests are located in tests/react/ and use the following tools:

Mock authentication context

The authentication context can be mocked for components that depend on it:

const auth: ReturnType<typeof AuthContext.useAuth> = {
  user: /* a user or null */,
  check: () => user != null,
  login: vi.fn(),
  logout: vi.fn(),
  register: vi.fn(),
};

vi.spyOn(AuthContext, "useAuth").mockImplementation(() => auth);

Using vi.fn() on methods allows you to check their calls:

expect(auth.login).toHaveBeenCalledWith(...);

Component mount testing

The tests primarily verify that the components are assembled correctly and that they perform as expected.

For example:

const Stub = stubRoute("/", Home);

render(<Stub initialEntries={["/"]} />);

await waitFor(() =>
  screen.getByRole("heading", { level: 1, name: /starter/i })
);

The goal is not to test the internal logic of React Router, but to ensure that the components work in a realistic environment.

To learn more about:

  • Calls like screen.getByRole(...) to find elements displayed on the screen, see the documentation about "queries".

  • Simulation of interactions, such as button clicks for example, see the introduction on user-event.

Best practices

A few principles guide the writing of tests:

  • Each test is isolated Mocked data and spies must be reset in beforeEach and afterEach.

  • Tests must reflect actual business logic Authorization, validation, and consistency rules must be explicitly tested.

  • Tests do not add unnecessary complexity Mocks should remain simple and controlled, without external dependencies.

  • Tests must remain stable and predictable No dependencies on external services or a real database.

  • The style should remain close to living documentation Each test provides a readable and consistent example of the expected behavior of the API or components.

CLI

To run all tests and collect coverage

docker compose run --build --rm server npm run test

To run a specific test folder, for example tests/react:

docker compose run --build --rm server npm run test react

To run a specific test file, for example tests/react/item.test.tsx:

docker compose run --build --rm server npm run test react/item
⚠️ **GitHub.com Fallback** ⚠️