Should I test that?

Should I test constants?

Verdict

No

Do not write a test that asserts the value of a constant such as a page size or an upload limit; test the behaviour that reads it, with the expected result written as a literal from the requirement.

Why

Do not write a test that asserts a constant's value; test the behaviour that reads it. For a typical constant such as MAX_UPLOAD_MB = 25, Blast radius is users and Change frequency is rarely, since a limit changes a few times a year. Detectability is same-day, because customers hit a wrong upload limit and support hears about it within a day. Reversibility is trivial: a revert leaves no data behind. Test cost is moderate: a test that can catch a wrong value must run the reading code against a number from the requirement. Rule R13 gives Do not test, and the case sits on the edge: if Detectability moves to eventually, the decision becomes Test minimally.

When the decision changes
WhenDecisionWhy
The constant holds a price or a tax rate, such as `VAT_RATE = 0.2`Test mandatory: assert the charged total for one orderBlast radius rises to money, Detectability moves to eventually and Reversibility to costly: wrong totals look plausible and refunds are manual
The constant sets a security limit, such as an access token lifetimeTest mandatory: assert that a token is rejected just after it expiresBlast radius rises to safety-or-legal and Detectability moves to never: a 30-day token keeps stolen sessions open without an error
A system you cannot update at once reads the value, such as a service that subscribes to an event nameTest minimally: one test that asserts the literal and names the readerDetectability moves to eventually and Reversibility to with-effort: a changed value passes your tests, and missed events need a replay
The reader assumes a unit, such as `CACHE_TTL = 24 * 60 * 60` passed to a client that expects millisecondsTest minimally: one test that checks the value the client receives, in millisecondsDetectability moves to eventually: a cache that expires after 86 seconds still returns correct data, only slower
A type carries the unit, such as `Duration.ofHours(24)`Do not test the constant; the compiler checks the unitDetectability rises to immediately: a bare number fails the build, so the decision stays Do not test
Only internal tooling reads the constant, such as a report script's batch sizeDo not test the constant; fix the value when a report looks wrongBlast radius falls to internal, so the decision stays Do not test

What breaks if you don't test

A value test would catch very little. The developer who types 25 in the constant also types 25 in the test, so a misread requirement passes both. A typo such as MAX_UPLOAD_MB = 2 makes ordinary phone photos fail with HTTP 413, and the first support ticket arrives the same afternoon.

What you lose if you over-test

A file of assertions such as expect(PAGE_SIZE).toBe(20) copies the constants module. Each intended change becomes two edits, and reviewers learn to update the test without reading it. The coverage report counts the constants as tested while the upload endpoint has no test at the limit.

What to do instead

Test the code that reads the constant, and write the expected value as a literal: a 26 MB upload returns 413 and a 25 MB upload succeeds. Put the unit in the name (CACHE_TTL_MS), or use a type that carries it, such as Java's Duration, so the compiler rejects a bare number. For a value that another system reads, add one assertion with Jest's toBe matcher and name the test after the reader.

When the answer changes

  • The value is money or a security limit, such as a tax rate or a token lifetime.
  • Another service, a partner, or an old app version reads the value.
  • The value carries a unit that its reader assumes.

Code example + Counterexample

The event name that another service reads

The usual advice, that a value test only restates the code, is wrong for the second constant here:

// constants.ts
export const PAGE_SIZE = 20;
// The billing service, deployed from another repository,
// subscribes to this exact name and sends a receipt per event.
export const ORDER_PAID_EVENT = "order.paid";

// constants.test.ts
test("page size", () => {
  expect(PAGE_SIZE).toBe(20); // restates the declaration: delete
});

test("billing service subscribes to order.paid", () => {
  expect(ORDER_PAID_EVENT).toBe("order.paid"); // pins a contract: keep
});

A developer renames the value to orders.paid to match a naming scheme. Every publisher reads the constant, so the build and every test that imports it pass. Billing still listens for the old name, receipts stop, and nobody notices until a customer asks for one. Detectability moves to eventually and Reversibility to with-effort, since the missed events need a replay, which gives Test minimally. The literal in the second test is this repository's copy of the subscriber's string, so a change to the constant alone fails CI, and the test name says which service to update first.

FAQ

Is it worth unit testing hardcoded values?

No, a unit test that asserts a hardcoded value repeats the declaration and fails only on intended changes. Test the behaviour that uses the value, with the expected result written as a literal.

Should unit tests cover constants changing?

No, a test that fails whenever an ordinary constant changes catches intended edits, and the developer copies the new value into it. Write one for a value that another service reads, and name the test after that service.

Should my tests use the constant or the literal value?

Behaviour tests should use the literal from the requirement, such as a 26 MB upload that must fail under a 25 MB limit. A test that builds its input from the constant passes for any value of the constant.

Should I test constants that hold a price or a tax rate?

Yes, test a price or a tax rate through the code that charges it: assert the total for one order, such as 120.00 for 100.00 at a 20% rate. Wrong totals look plausible and refunds are manual, so the framework makes this test mandatory.