---
title: "Test Code Duplication"
type: "test-smell"
slug: "test-code-duplication"
url: "http://localhost:3000/en/test-smells/test-code-duplication.md"
category: "Obscure Smells"
description: "Test Code Duplication is when the same setup, action, or assertion code is copy-pasted across many tests, so a single change forces edits in many places and tests rot into fragile, near-identical copies."
---
# Test Code Duplication

> Test Code Duplication is when the same setup, action, or assertion code is copy-pasted across many tests, so a single change forces edits in many places and tests rot into fragile, near-identical copies.

## Signs and Symptoms

You recognize this smell when tests look like they were written with copy-paste rather than reuse:

* The **same fixture/object construction** is rebuilt verbatim at the top of test after test.
* **Identical assertion sequences** (the same 3–4 `expect` calls in the same order) recur across tests.
* New tests are obviously cloned from an old one with a single line tweaked.
* The **same magic literals** (IDs, URLs, dates, error strings) appear over and over.
* A change to one constructor or API signature breaks **dozens of tests at once** (shotgun surgery).
* You see **near-duplicate tests that differ only in input/expected values** — a clear candidate for a parameterized/table test.

```js
test('flight can be cancelled', () => {
  const airport = new Airport('YYC', 'Calgary');                              // duplicated
  const flight  = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // duplicated
  flight.cancel();
  expect(flight.status).toBe('CANCELLED');
});

test('flight can be delayed', () => {
  const airport = new Airport('YYC', 'Calgary');                              // copy-paste
  const flight  = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // copy-paste
  flight.delay(30);
  expect(flight.status).toBe('DELAYED');
});

```

## Reasons for the Problem

**Why it happens**

* Copy-pasting the previous test is the fastest way to write the next one.
* Tests are treated as a "second-class citizen" — not refactored or held to the same DRY standard as production code.
* No shared fixtures, Creation Methods, or test-data builders exist, and authors are unfamiliar with parameterized/table-driven tests.

**Why it hurts**

* **Maintainability:** Meszaros ties this smell directly to _Fragile Test_ — when "the same code sequences appear many times in many tests," one production change means editing the same thing in N places. Maintenance cost scales with the number of copies, not with the number of distinct behaviors.
* **Readability:** repeated boilerplate buries the one line that actually makes each test different, so readers can't quickly see what is being verified.
* **Reliability:** copy-paste invites copy-paste errors, and fixes get applied to one copy but not its siblings, leaving inconsistent, contradictory tests.
* **False confidence:** a flawed assertion that was duplicated is now wrong in many places at once, and cloned tests silently drift until they no longer exercise what their names claim.

**Caveat — DRY vs DAMP:** tests also value being _Descriptive And Meaningful Phrases_. Don't over-abstract to the point that a reader must chase helpers to understand a test. Extract genuine, intent-revealing duplication; keep the essential, per-test detail visible and local.

## Treatment

Eliminate the _incidental_ duplication while keeping each test's essence obvious:

1. **Extract Test Utility / Creation Methods** (Object Mother, Test Data Builder) for repeated object construction, so each test names only the values it cares about.
2. **Use `beforeEach` / Implicit Setup** for context that is genuinely shared and relevant to every test in the block — but avoid hiding state a test depends on (that trades duplication for an _Obscure Test_).
3. **Extract Custom Assertions / verification helpers** for recurring multi-step assertion sequences, ideally verifying one logical condition.
4. **Collapse near-identical tests into Parameterized / table-driven tests** (`it.each` / `test.each`) so input + expected output pairs live in one table.
5. **Replace duplicated magic literals** with named constants or builder defaults.

Before — duplicated construction and three near-identical tests:

```js
test('rejects negative amount', () => {
  expect(() => validateAmount(-1)).toThrow(RangeError);
});
test('rejects zero amount', () => {
  expect(() => validateAmount(0)).toThrow(RangeError);
});
test('rejects NaN amount', () => {
  expect(() => validateAmount(NaN)).toThrow(RangeError);
});

```

After — a Creation Method removes setup duplication, and a table replaces the clones:

```js
// shared Creation Method: tests state only the overrides that matter
const aFlight = (overrides = {}) =>
  new Flight('AC123', new Airport('YYC', 'Calgary'),
             new Date('2026-06-01T10:00'), overrides);

it.each([-1, 0, NaN])('rejects invalid amount %p', (amount) => {
  expect(() => validateAmount(amount)).toThrow(RangeError);
});

```

Run a copy-paste detector (below) over your test sources, then refactor the largest/most-repeated blocks first.

## Detected by

- **eslint-sonarjs** `no-identical-functions` — Functions should not have identical implementations (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-identical-functions.md)
- **eslint-sonarjs** `no-duplicate-string` — String literals should not be duplicated (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-duplicate-string.md)
- **sonar** `javascript:S4144` — Functions should not have identical implementations (https://rules.sonarsource.com/javascript/RSPEC-4144/)
- **pmd** `cpd` — Copy/Paste Detector (CPD) — duplicated code blocks (https://pmd.github.io/pmd/pmd_userdocs_cpd.html)
