---
title: "For Testers Only"
type: "test-smell"
slug: "for-testers-only"
url: "http://localhost:3000/en/test-smells/for-testers-only.md"
category: "Coupling Smells"
description: "Production code contains methods, state accessors, or seams that exist only to be used by tests, polluting the real API and inviting tests that verify internals instead of behavior."
---
# For Testers Only

> Production code contains methods, state accessors, or seams that exist only to be used by tests, polluting the real API and inviting tests that verify internals instead of behavior.

## Signs and Symptoms

You find members in **production** code whose only callers live in test files. Tell-tale signs:

* Methods, getters/setters, exports, or constructor parameters used exclusively from `*.test.ts` / `*.spec.ts`.
* Test-flavored names or markers: `getStateForTest`, `resetForTesting`, `__getInternal`, `FTO_*`, `forTest`, comments like `// only used by tests`, or annotations such as `@VisibleForTesting` / `@TestOnly` / `@internal`.
* Visibility relaxed (a field made `public`/exported, `#private` turned `protected`) purely so a test can reach internal state.
* Extra "seams" — a `setClock(...)`, `setRandom(...)`, or `reset()` — added only because a test needed them, not because the real design calls for them.

```ts
// payment-service.ts  (PRODUCTION code)
export class PaymentService {
  #ledger: Entry[] = [];

  charge(amount: number) { /* ... */ }

  // Nothing in production ever calls these — only the tests do:
  getLedgerForTest() { return this.#ledger; }          // exposes internals
  setClockForTest(now: () => Date) { this.now = now; } // test-only seam
  FTO_reset() { this.#ledger = []; }                   // "For Tests Only"
}

```

A quick check: `grep -rn 'forTest\|ForTesting\|FTO_' src/` that returns hits in production source, or a dead-code pass (e.g. Knip in production mode) that reports an export as unused while a test clearly imports it.

## Reasons for the Problem

**Why it happens**

* _Retrofitting tests onto untestable code._ When legacy code wasn't designed for testability, the fastest way to assert on a result is to punch a hole into the SUT and read its internals — Meszaros lists this as the prime cause of _For Tests Only_.
* _Asymmetric APIs._ Real clients use an object one way (write); tests use it symmetrically (write **then** read back to verify), so testers "need" accessors that no production caller needs.
* _Schedule pressure._ Adding a back door is cheaper in the moment than refactoring to a design where behavior is observable through the public contract.

**Why it hurts**

* _Readability._ The public API no longer tells the truth — maintainers can't distinguish the real contract from test scaffolding, and every reader has to wonder "is this method actually used?"
* _Encapsulation & reliability._ Internal state becomes reachable and mutable in shipping code. A stray call to `FTO_reset()` or a leaked setter can corrupt state in production; the extra surface also bloats the bundle and widens the attack surface.
* _False confidence._ Tests that poke private state assert on _implementation_, not _behavior_. They can stay green while the public contract is broken, and they break on harmless refactors — fragile tests that test the wrong thing.
* _Maintainability._ The test-only members look like dead code but can't be deleted; every change has to account for phantom callers, and the smell tends to multiply as more tests reuse the back door.

## Treatment

Treat the back door as a design signal, not a fixture detail.

1. **Test through observable behavior first.** Assert on return values, emitted events, persisted output, or interactions with collaborators (via test doubles) instead of reaching into internals. Most `getXForTest` accessors disappear once you verify what the object _does_, not what it _holds_.
2. **Use a Test-Specific Subclass** when you genuinely need internal access. Extend the class in the test to expose a `protected` member, instead of widening production visibility.
3. **Make seams part of the real design, not test-only hatches.** Injecting a clock or RNG through the normal constructor is legitimate dependency injection; a `setClockForTest()` mutator is a smell. If a seam only makes sense for tests, push the behavior into a **Strategy/Null Object** that production installs by default and the test swaps out.
4. **If exposure is truly unavoidable, label it loudly and fence it off.** Mark it `@VisibleForTesting` / `@internal` (or an `FTO_` naming convention) and enforce that production code never calls it (see detectors). A marked, guarded seam beats a silent one.
5. **Hunt existing offenders.** `grep` for `forTest`/`ForTesting`/`FTO_`, and run a usage/dead-code tool such as Knip in production mode to surface exports referenced only by test files.

```ts
// BEFORE — production carries a test-only accessor
export class Cart {
  #items: Item[] = [];
  add(i: Item) { this.#items.push(i); }
  getItemsForTest() { return this.#items; } // for testers only
}
// test
expect(cart.getItemsForTest()).toHaveLength(1);

```

```ts
// AFTER — assert behavior through the real contract
export class Cart {
  #items: Item[] = [];
  add(i: Item) { this.#items.push(i); }
  get count() { return this.#items.length; }
  get total() { return this.#items.reduce((s, i) => s + i.price, 0); }
}
// test
cart.add({ price: 10 });
expect(cart.count).toBe(1);
expect(cart.total).toBe(10);

```

If you still need internal access, narrow the seam to tests with a subclass rather than the whole world:

```ts
// production stays clean: queue is protected, not public
export class Scheduler {
  protected queue: Job[] = [];
  enqueue(j: Job) { this.queue.push(j); }
}
// test file only
class TestScheduler extends Scheduler {
  peek() { return this.queue; }
}

```

## Detected by

- **sonar** `java:S5803` — "@VisibleForTesting" members should not be accessed from production code (https://rules.sonarsource.com/java/RSPEC-5803/)
