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.
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.
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).
The tests in the tests/setup/ folder validate the proper functioning of the minimal environment:
- existence and content of the
.envfile - 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 are organized by module (auth, items...) and use a full Express environment, but with a completely mocked 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:
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.
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 are located in tests/react/ and use the following tools:
-
React Testing Library for component rendering.
-
createRoutesStub from
react-routerto simulate navigation (see the React Router documentation). -
A
fetchmock to avoid network requests (see tests/api/utils.ts).
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(...);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.
A few principles guide the writing of tests:
-
Each test is isolated Mocked data and spies must be reset in
beforeEachandafterEach. -
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.
To run all tests and collect coverage
docker compose run --build --rm server npm run testTo run a specific test folder, for example tests/react:
docker compose run --build --rm server npm run test reactTo run a specific test file, for example tests/react/item.test.tsx:
docker compose run --build --rm server npm run test react/item