Should I test that?

Should I test code that is already covered by other tests?

Verdict

No

No, do not add a test for code whose results the tests of its callers already assert in CI, with data that gives each branch a different result; add a direct test only for a branch or a result that those tests never check.

Why

No, do not add a test for code that other tests already check; add one where they run the code without checking its result. The typical case is a helper whose callers' tests run in CI and assert results that differ for each of its branches. Blast radius is users, and Change frequency is regularly, because the helper changes with its callers. Detectability is immediately and Reversibility is trivial: a change that breaks the helper fails a caller's test before merge. Test cost is moderate, because a direct test needs its own update each time the behaviour changes, so rule R8 gives Do not test; when the covering tests only run the code, Detectability moves to eventually and the decision becomes Test.

When the decision changes
WhenDecisionWhy
The covering tests assert only a status code, or only that nothing throwsTest: assert the result of the codeDetectability moves to eventually and Reversibility to with-effort, because a wrong saved value passes those tests
A method saves on each shipment the later of two dates that tested methods returnTest the rule that picks the date, not the cases of the called methodsDetectability moves to eventually and Reversibility to with-effort: no test runs the picking rule, and a wrong saved date looks plausible
A branch that no covering test takes throws on a page, such as a profile with no time zoneTest minimally: one direct test of that branchDetectability moves to same-day, because users report the error
The covered code rounds invoice amountsTest mandatory: each rounding rule and boundary amountBlast radius rises to money and Reversibility to costly
The covered code decides who may open another tenant's recordTest mandatory: one allowed and one denied request for each roleBlast radius rises to safety-or-legal and Detectability to never, because a leak raises no error
The callers' tests give each branch a different result, and a direct test would take two minutesDo not add the direct testTest cost falls to trivial, but Detectability stays immediately and Reversibility trivial

What breaks if you don't test

Tests of other code protect a helper only for the inputs they pass and the results they check. A new caller can use the helper for a case those tests never assert. A caller can be rewritten and its tests deleted, leaving the helper with no test while other code still calls it. CI stays green after both changes, and the first sign is a wrong result that a user notices days later.

What you lose if you over-test

A direct test that repeats what the callers' tests assert catches no new bug, and each change in behaviour means editing both. Renaming one output field in a shared formatter then breaks the formatter's test and every endpoint test that checks the field.

What to do instead

  1. Check that the covering tests assert the code. In coverage.py, set dynamic_context = test_function and build the HTML report with --show-contexts to see which tests run each line.
  2. Break a line on purpose, or run a mutation tool such as PIT, and watch whether a covering test fails; if none fails, write one.
  3. For a method that combines tested methods, test the combining rule only.
  4. When you delete a test file, compare coverage before and after, and give a direct test to code that lost its last covering test.

When the answer changes

  • The covering tests check only a status code, or run nightly instead of on each pull request.
  • The code gains a caller whose use the covering tests do not assert.
  • The code computes money or decides who may see a record.

Real incident + Code example

The date range that lost its tests

On a B2B analytics product I worked on, rangeFor() turned presets such as "last 30 days" into dates. Only the dashboard endpoint's tests covered it. When we replaced that endpoint, its tests went with it and left rangeFor() with none, while the CSV export still called it. Two months later a developer made "last 30 days" end yesterday for a new chart that showed only complete days. The export dropped the current day's sign-ups, and a customer who compared exports with her own counts reported the gap 11 days later. The direct test we added afterwards fails on that change:

import { expect, test } from "vitest";
import { rangeFor } from "./ranges";

// Belongs to rangeFor, so deleting a caller and its tests leaves it in place
test("last 30 days ends today and includes it", () => {
  expect(rangeFor("last-30-days", "2026-03-31")).toEqual({
    from: "2026-03-02",
    to: "2026-03-31",
  });
});

FAQ

Should I test a method that calls a method that is already tested?

Yes, test the rule that the calling method adds, such as which result it picks, and leave the cases of the called methods to their own tests. A method that only forwards a call needs no test when a caller's test runs it.

Should I write tests for a class that is covered by the tests of another class?

No, a class whose results another class's tests assert in CI, with data that gives each branch a different result, needs no tests of its own. Test directly the branches those tests skip and any code that computes money or decides access.

Does code coverage from other tests count as testing?

Coverage from other tests counts as testing only for lines whose results those tests assert. A coverage report shows that a line ran, not that a test fails when the line is wrong.

How do I know which tests cover a line of code?

The coverage.py tool for Python records the test behind each line when dynamic_context = test_function is set, and its HTML report lists those tests with --show-contexts. In Java, the PIT mutation report names the test that killed each mutant.