← React / TypeScript

React / TypeScript · 15 / 18

Test components and repositories meaningfully

Verify a user action and identify failures only real database tests can find.

  • Medium
  • Intermediate
  • 45 min

Prerequisites: HTTP contracts and runtime validation

A component test should confirm what a user can see after an action. It does not need to know every internal state. A repository test instead checks persistence, relationships and rollback. House Log uses real SQLite databases for those contracts. The small browser example checks normalised form submission from tutorial 11.

Example files

01Set up the test

Use the React project from tutorial 10 with App.tsx from tutorial 11. package.json contains Vitest, Testing Library and jsdom. Save the test as src/App.test.tsx. The environment comment enables a browser DOM for this test.

src/App.test.tsx · tsxOpen file
// @vitest-environment jsdom
import React from 'react';
import { afterEach, expect, test } from 'vitest';
import { cleanup, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { App } from './App';
afterEach(cleanup);
test('confirmed input is normalised and displayed', async () => {
  render(<App />);
  const user = userEvent.setup();
  await user.type(screen.getByLabelText('Title'), '  Roof  ');
  await user.click(screen.getByRole('button', { name: 'Add' }));
  expect(screen.getByRole('listitem').textContent).toBe('Roof');
});
test('blank input creates no record', async () => {
  render(<App />);
  await userEvent.click(screen.getByRole('button', { name: 'Add' }));
  expect(screen.queryAllByRole('listitem')).toHaveLength(0);
  expect(screen.getByRole('alert').textContent).toBe('Title required');
});

Run / check

npm test

02Test through accessible roles

getByLabelText finds the field through its accessible label. getByRole finds the actual button. userEvent reproduces typing and clicking. We then check for a listitem containing normalised text.

03Check empty input

The second test clicks without input. No entry may be created. The error message must appear instead. cleanup separates runs so a previous screen cannot affect the next.

04Add the appropriate boundaries

For the real repository, test foreign keys, migrations, transaction rollback and reopening. Camera and scanner require native tests or a documented device workflow. A jsdom test proves neither SQLite persistence nor Android permissions.

Expected result

Both tests pass. Removing trim or bypassing blank-input validation must fail at least one test.

Your exercise

Test that successful submission clears the input and a failure retains it.

Show the solution and explanation

Read the field as HTMLInputElement and check value. Type only spaces for the failure case. After failed submission they must remain in the field. This protects the draft against unintended loss.

Project context and sources

The example is simplified for learning. The linked source version shows its use in the project and the checks applied there.