---
title: "Unknown Test"
type: "test-smell"
slug: "unknown-test"
url: "http://localhost:3000/en/test-smells/unknown-test.md"
category: "Assertion Smells"
description: "A test method that exercises the code but contains no assertion, so it passes as long as nothing throws — leaving its actual purpose and what it verifies unknown."
---
# Unknown Test

> A test method that exercises the code but contains no assertion, so it passes as long as nothing throws — leaving its actual purpose and what it verifies unknown.

## Signs and Symptoms

A test that sets up objects and calls the system under test, then **stops without a single assertion**. It is green simply because no exception was thrown — not because any expected behavior was confirmed. Nothing in the body states what "correct" looks like.

Tell-tale signs:

* No `expect`/`assert`/`verify` anywhere in the test body.
* "Verification" done by `console.log`/`print` of the result that a human is supposed to eyeball.
* A vague test name (`testChainDependencies`) and a body that gives no hint of what it guarantees.
* The test would stay green even if the production method returned a completely wrong value.
* Tests that lean entirely on "it didn't throw" without saying so explicitly.

```js
// Smell: runs code, prints, asserts nothing — passes no matter what calculate() returns
test('chain dependencies', () => {
  const game = Game.newGame(0, '');
  game.setOtherGoods(Building.TOOLMAKERS, 1);
  const logic = new Logic(game);

  const res = logic.calculateChainWithDependencies(Goods.TOOLS);
  console.log(res.toString()); // no expect(...) — what is this checking?
});

```

A common variant hides behind heavy mocking: the test wires up mocks and calls the SUT but never asserts on a return value _or_ on the mock interactions.

## Reasons for the Problem

**Why it happens**

* A placeholder/TODO test was scaffolded ("make it compile and run") and the assertion was never filled in.
* Debugging scaffolding — `console.log`, a manual sanity run — got committed as if it were a test.
* A refactor deleted or commented out the assertion but left the setup behind.
* "It runs without an exception" is mistaken for "it works." Smoke tests are legitimate, but here the _intent_ to smoke-test is never made explicit.
* Auto-generated or AI-generated test stubs that exercise a method without checking the outcome.

**Why it hurts**

* **False confidence.** The test contributes to the green bar and to line/branch coverage, yet verifies nothing. Coverage metrics actively lie about how protected the code is.
* **No regression protection.** Behavior can silently break — wrong return value, wrong state — and the suite stays green. This is the worst kind of test: it costs maintenance but catches nothing.
* **Unknown intent.** A reader (or future maintainer) cannot tell which behavior is guaranteed, so they can't safely change the code or the test. It documents nothing.
* **Erodes trust in the suite.** Once people notice tests that don't really test, they stop believing green means good — undermining every other test too.

It is the mirror image of [Assertion Roulette](#): that smell has _too many_ undocumented assertions; Unknown Test has _none_.

## Treatment

Make every test state what it expects, and make "it just shouldn't throw" an explicit, deliberate choice.

1. **Add at least one assertion on an observable outcome** — the return value, resulting state, or a thrown error. Replace `console.log`/print debugging with an `expect` on that value.
2. **If the real intent is "this must not throw," say so explicitly** with `expect(() => fn()).not.toThrow()` (or `await expect(fn()).resolves.toBeDefined()`). Now the smoke test is documented rather than accidental.
3. **For interaction-only tests**, assert on the collaborator: `expect(mock).toHaveBeenCalledWith(...)`.
4. **Delete or `skip`/`todo` dead placeholders** instead of leaving an empty green test (`it.todo('handles chained deps')` records the gap honestly without faking coverage).
5. **Turn on a detector** (`jest/expect-expect`, `vitest/expect-expect`, or SonarSource S2699) in CI. If you wrap assertions in custom helpers, register them via the rule's `assertFunctionNames` option so genuine assertions aren't flagged.

```js
// Before — Unknown Test
test('chain dependencies', () => {
  const logic = new Logic(Game.newGame(0, ''));
  const res = logic.calculateChainWithDependencies(Goods.TOOLS);
  console.log(res.toString());
});

// After — intent and guarantee are explicit
test('resolves tools to the toolmakers workshop chain', () => {
  const logic = new Logic(Game.newGame(0, ''));
  const res = logic.calculateChainWithDependencies(Goods.TOOLS);
  expect(res).toHaveLength(1);
  expect(res[0].building).toBe(Building.TOOLMAKERS);
});

```

## Detected by

- **eslint-jest** `expect-expect` — jest/expect-expect (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/expect-expect.md)
- **eslint-vitest** `expect-expect` — vitest/expect-expect (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md)
- **sonar** `S2699` — Tests should include assertions (JavaScript) (https://rules.sonarsource.com/javascript/RSPEC-2699/)
- **sonar** `S2699` — Tests should include assertions (Java) (https://rules.sonarsource.com/java/RSPEC-2699/)
