← Front-End Developer — all chapters  ·  Kelick Tech Hub

Chapter 34 — Testing Front-End Code

Section 1 — What a test is

A test is a small, repeatable check that a piece of code behaves as intended. It runs the code with known inputs and compares the result with the expected result, so a failure points to a specific behaviour, not a vague feeling. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet what a test is, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If what a test is is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of what a test is, and when would you use it?

Section 2 — Why test front-end code

Front-end code is easy to break by accident, because a change in one place can alter what users see elsewhere. Tests catch those regressions early, while the change is still fresh, instead of letting users discover them. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet why test front-end code, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If why test front-end code is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of why test front-end code, and when would you use it?

Section 3 — Unit tests

A unit test checks one small unit in isolation, such as a single function that formats a price or filters a list. Small units are fast to test and failures are easy to locate, because only a little code is involved. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet unit tests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If unit tests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of unit tests, and when would you use it?

Section 4 — Integration tests

An integration test checks how several parts work together, such as a form component with its validation and submission logic. It catches problems that unit tests miss, where each part is correct alone but they disagree when combined. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet integration tests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If integration tests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of integration tests, and when would you use it?

Section 5 — End-to-end tests

An end-to-end test drives the real page in a browser, like a user would: it visits a page, clicks, types, and checks what appears. It is the closest test to real use, and also the slowest and most fragile kind. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet end-to-end tests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If end-to-end tests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of end-to-end tests, and when would you use it?

Section 6 — The testing pyramid

The testing pyramid suggests many fast unit tests, fewer integration tests, and few end-to-end tests. The shape reflects cost: unit tests are cheap and quick, while browser tests are slow, flaky, and expensive to maintain. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet the testing pyramid, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If the testing pyramid is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of the testing pyramid, and when would you use it?

Section 7 — What to test first

Test first the behaviour that matters most if it breaks: calculations, validation, and the main user journeys. A page with perfect tests for trivia but no test for its checkout or submission flow is not well tested. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet what to test first, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If what to test first is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of what to test first, and when would you use it?

Section 8 — What not to test

Do not test the framework itself, third-party libraries, or trivial details such as the exact wording of a class name that may change. Tests should protect behaviour users care about, not freeze every internal detail in place. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet what not to test, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If what not to test is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of what not to test, and when would you use it?

Section 9 — Assertions

An assertion is the statement in a test that says what must be true, such as that a result equals an expected value. A test without a clear assertion proves nothing, however much code it runs. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet assertions, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If assertions is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of assertions, and when would you use it?

Section 10 — Test runners

A test runner finds test files, runs them, and reports which passed and which failed. It also provides the functions for grouping tests and making assertions, so you write the checks and the runner handles the machinery. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet test runners, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If test runners is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of test runners, and when would you use it?

Section 11 — Writing your first test

A first test usually has three steps: arrange the inputs, act by calling the code, and assert the result. That arrange-act-assert pattern keeps tests readable, because every test tells the same kind of short story. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet writing your first test, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If writing your first test is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of writing your first test, and when would you use it?

Section 12 — Red, green, refactor

Red, green, refactor is the test-driven rhythm: write a failing test first, make it pass with the simplest code, then tidy the code without changing its behaviour. The failing test proves the test can actually detect the problem. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet red, green, refactor, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If red, green, refactor is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of red, green, refactor, and when would you use it?

Section 13 — Test-driven development

Test-driven development means writing the test before the implementation, so the test describes the behaviour you want. It forces small, clear design decisions, because code that is hard to test is often hard to use. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet test-driven development, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If test-driven development is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of test-driven development, and when would you use it?

Section 14 — Testing a function

To test a function, call it with representative inputs, including edge cases such as empty lists and boundary numbers, and assert each output. A function with no side effects is the easiest kind of code to test well. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing a function, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing a function is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing a function, and when would you use it?

Section 15 — Testing a component

To test a component, render it, interact with it as a user would, and assert what appears. Test what the user sees and can do, not the component's internal variables, so the test survives harmless refactors. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing a component, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing a component is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing a component, and when would you use it?

Section 16 — Queries users would use

Good component tests find elements the way users do: by visible text, by label, or by role, such as a button. Queries based on internal test identifiers are a fallback, not a first choice, because they test structure instead of experience. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet queries users would use, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If queries users would use is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of queries users would use, and when would you use it?

Section 17 — Simulating clicks and typing

Simulating user actions means firing the same events a real user would, such as clicking a button or typing into a field, then checking the result. This tests the interaction, not just the initial render. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet simulating clicks and typing, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If simulating clicks and typing is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of simulating clicks and typing, and when would you use it?

Section 18 — Testing forms

Form tests check that valid input can be submitted, invalid input shows a helpful message, and submission does not happen with bad data. They should cover both the happy path and the most likely mistakes a user will make. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing forms, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing forms is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of testing forms, and when would you use it?

Section 19 — Testing validation

Validation tests feed each rule its boundary cases: too short, too long, missing, and just right. A rule tested only with a clearly valid and a clearly invalid value may still fail at the exact boundary where users stumble. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing validation, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing validation is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing validation, and when would you use it?

Section 20 — Testing lists and rendering

List tests check that items render in the right order, that an empty list shows its empty state, and that adding or removing an item updates the display. They protect the mapping from data to screen, which is central to front-end work. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing lists and rendering, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing lists and rendering is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of testing lists and rendering, and when would you use it?

Section 21 — Testing conditional display

Conditional display tests check that each branch appears when its condition holds and hides when it does not, such as a menu that opens and closes. Every branch deserves a test, because an untested branch is where stale logic hides. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing conditional display, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing conditional display is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing conditional display, and when would you use it?

Section 22 — Mocks in plain words

A mock is a stand-in for a real dependency, such as a network request or a timer, that behaves predictably during a test. Mocks let you test your code without waiting for a server or depending on outside systems. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet mocks in plain words, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If mocks in plain words is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of mocks in plain words, and when would you use it?

Section 23 — Mocking network requests

Mocking a network request means intercepting the call and returning a fixed response, so the test checks how your code handles success, failure, and slow data without needing a live server. The mock should resemble the real response closely. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet mocking network requests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If mocking network requests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of mocking network requests, and when would you use it?

Section 24 — Faking timers and dates

Timers and dates make tests unpredictable if left real, because results depend on when the test runs. Faking them lets you advance time on demand and check behaviour such as debouncing or expiry without waiting. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet faking timers and dates, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If faking timers and dates is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of faking timers and dates, and when would you use it?

Section 25 — Spies and stubs

A spy records how a function was called, while a stub returns a fixed value. Spies answer questions like whether a callback ran and with what arguments; stubs answer questions about behaviour under controlled inputs. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet spies and stubs, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If spies and stubs is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of spies and stubs, and when would you use it?

Section 26 — Snapshot tests

A snapshot test records a component's output and fails if it changes. Snapshots catch unintended changes quickly, but they must be reviewed carefully, because approving a snapshot blindly turns the test into a rubber stamp. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet snapshot tests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If snapshot tests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of snapshot tests, and when would you use it?

Section 27 — When snapshots help and hurt

Snapshots help for stable, small outputs where any change is suspicious, and hurt for large, changing outputs where every run produces noise. A snapshot that fails constantly for harmless reasons will soon be ignored. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet when snapshots help and hurt, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If when snapshots help and hurt is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of when snapshots help and hurt, and when would you use it?

Section 28 — Testing accessibility

Accessibility tests check that interactive elements have names, that forms have labels, and that the page structure is usable with a keyboard and screen reader. Automated checks catch common problems, but they cannot replace manual testing. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing accessibility, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing accessibility is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing accessibility, and when would you use it?

Section 29 — Testing keyboard interaction

Keyboard tests check that focus moves sensibly, that menus and dialogs can be operated without a mouse, and that focus returns where it came from. Keyboard users are real users, and a page that traps focus is broken for them. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing keyboard interaction, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing keyboard interaction is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing keyboard interaction, and when would you use it?

Section 30 — Testing loading states

Loading tests check that a pending request shows a loading indicator, not a blank screen or stale data, and that the final data replaces it. They often use a delayed mock response so the loading state is actually visible. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing loading states, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing loading states is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing loading states, and when would you use it?

Section 31 — Testing error states

Error tests mock a failed request and check that the user sees a clear message and a way to retry. Code that handles success well but crashes on failure is only half finished, and error tests prove the other half. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing error states, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing error states is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing error states, and when would you use it?

Section 32 — Testing empty states

Empty tests check that a list with no items, or a search with no matches, shows a helpful empty message rather than a broken layout. Empty is a legitimate state, not an error, and it deserves its own test. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing empty states, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing empty states is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of testing empty states, and when would you use it?

Section 33 — Testing navigation

Navigation tests check that following a link or submitting a search lands on the expected screen with the expected content. They protect the routes and links that hold a multi-page flow together. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing navigation, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing navigation is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing navigation, and when would you use it?

Section 34 — Testing routing

Routing tests check that each URL renders the right view, that unknown URLs show a not-found page, and that parameters such as an identifier are read correctly. A wrong route is invisible in a unit test of a single component. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing routing, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing routing is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing routing, and when would you use it?

Section 35 — Testing responsive behaviour

Responsive tests check that a component behaves at different widths, such as a menu collapsing on narrow screens. They cannot fully replace checking in a real browser, but they catch logic that depends on size. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing responsive behaviour, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing responsive behaviour is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing responsive behaviour, and when would you use it?

Section 36 — Testing hooks and state

State tests check that an interaction moves state from one value to the next correctly, including repeated actions and resets. Testing the state transitions directly is often clearer than testing only the final screen. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing hooks and state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing hooks and state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of testing hooks and state, and when would you use it?

Section 37 — Flaky tests

A flaky test is one that sometimes passes and sometimes fails without any code change, often because of timing, order, or shared state. Flaky tests erode trust in the whole suite, so they must be fixed or removed, not tolerated. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet flaky tests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If flaky tests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of flaky tests, and when would you use it?

Section 38 — Making tests deterministic

Deterministic tests give the same result every time: they control time, randomness, network, and shared state, and they clean up after themselves. A test that depends on the clock or on another test is a test waiting to fail. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet making tests deterministic, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If making tests deterministic is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of making tests deterministic, and when would you use it?

Section 39 — Test setup and teardown

Setup prepares what a test needs, such as rendering a component or creating data, and teardown removes it afterwards. Clean setup and teardown keep tests independent, so one test cannot poison the next. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet test setup and teardown, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If test setup and teardown is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of test setup and teardown, and when would you use it?

Section 40 — Test data and fixtures

Fixtures are reusable sets of test data, such as a typical user or a typical list of items. Good fixtures are small, realistic, and clearly named, so a reader can tell what a test is really checking. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet test data and fixtures, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If test data and fixtures is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of test data and fixtures, and when would you use it?

Section 41 — Organising test files

Test files are usually kept near the code they test or in a parallel test folder, with names that make the pairing obvious. A test that cannot be found will not be maintained, however good it is. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet organising test files, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If organising test files is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of organising test files, and when would you use it?

Section 42 — Naming tests well

A test name should describe the behaviour and the situation, such as what happens when a form is submitted empty. A name like 'test one' tells a future reader nothing when it fails, while a behaviour name points straight at the problem. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet naming tests well, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If naming tests well is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of naming tests well, and when would you use it?

Section 43 — One idea per test

Each test should check one idea, so a failure names one problem. A test that checks five things at once fails with a vague message and must be read in full to learn what actually broke. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet one idea per test, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If one idea per test is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of one idea per test, and when would you use it?

Section 44 — Test coverage

Coverage measures which lines or branches your tests execute, not whether they are correct. It is a useful guide to untested areas, but a high coverage number with weak assertions is a false sense of safety. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet test coverage, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If test coverage is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of test coverage, and when would you use it?

Section 45 — Reading a failing test

Reading a failing test starts with the failure message and the line it points to, then the test's intent, then the code under test. Resist the urge to change the test to match the code until you know which one is wrong. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet reading a failing test, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If reading a failing test is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of reading a failing test, and when would you use it?

Section 46 — Debugging with tests

A failing test is a small, repeatable reproduction of a bug, which makes debugging far easier than reproducing it by hand in a browser. Narrow the test until it shows the smallest case that fails, then fix that case. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet debugging with tests, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If debugging with tests is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

expect(add(2, 3)).toBe(5);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of debugging with tests, and when would you use it?

Section 47 — Continuous integration testing

Continuous integration runs the test suite automatically whenever code is pushed, so a broken change is caught before it reaches users. A suite that is slow, flaky, or ignored in this setting protects nobody. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet continuous integration testing, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If continuous integration testing is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

render(<SearchBox />);
fireEvent.change(input, { target: { value: 'test' } });

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of continuous integration testing, and when would you use it?

Section 48 — Testing in the browser

Browser tests run against the real page in a real browser, catching problems that a simulated environment misses, such as layout, real events, and browser differences. They are slower, so they are reserved for the most important journeys. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing in the browser, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If testing in the browser is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

test('shows an error when submitted empty', () => {
  expect(screen.getByText('Required')).toBeInTheDocument();
});

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

Check: In your own words, what is the key idea of testing in the browser, and when would you use it?

Section 49 — Visual regression testing

Visual regression testing compares screenshots of a page with approved reference images and flags pixel differences. It catches styling accidents that functional tests miss, but it needs care with fonts, animations, and screen sizes. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet visual regression testing, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If visual regression testing is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

jest.mock('./api');
api.fetchItems.mockResolvedValue([{ id: 1 }]);

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of visual regression testing, and when would you use it?

Section 50 — Keeping tests maintainable

Maintainable tests use helpers for repeated setup, avoid duplicating the implementation's logic, and test behaviour rather than internals. A suite that is painful to update will be deleted, and then its protection disappears with it. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet keeping tests maintainable, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.

Why does this matter in real work? Because front-end code is judged by what users experience. If keeping tests maintainable is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.

How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.

Here is a short example to make the idea concrete:

await user.click(screen.getByRole('button', { name: 'Submit' }));

Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.

A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.

A useful habit is to run the test, watch it fail for the right reason, and only then make it pass, because a test that never fails may not be testing anything at all.

When a test is hard to write, treat that difficulty as information about the code under test, and consider whether the code itself could be simpler or better separated.

Check: In your own words, what is the key idea of keeping tests maintainable, and when would you use it?

Practical task

Build a small page that puts this chapter into practice. For Chapter 34, testing front-end code, create one page with a clear example of the chapter's main ideas, try it in the browser, break one thing deliberately and fix it, and write a short note explaining what you learned and what you would do differently next time. Keep the page small, test it on a narrow screen as well as a wide one, and check that every interaction still behaves as you intended.

Chapter questions

  1. Explain, in plain words, the central idea of Chapter 34 and why it matters to users, not just to developers.
  2. Give an example where doing this chapter's topic badly would be visible to a user, and describe what the user would experience.
  3. Compare two approaches from this chapter and explain when you would choose each one.
  4. Describe a mistake a beginner commonly makes with this chapter's topic, how you would spot it, and how you would fix it.
  5. Build a tiny example that demonstrates one idea from this chapter, and explain, step by step, how you would check that it works correctly.