Verdict
No
Do not test a constructor that only assigns arguments of different types to fields, or one the language generates; test the methods that use those fields.
Why
- Blast radiususers
- Change frequencyrarely
- Detectabilityimmediately
- Reversibilitytrivial
- Test costtrivial
I do not test a constructor that only stores arguments of different types, or one the language generates, such as a record or a data class. Detectability is immediately: the compiler rejects an argument assigned to a field of another type, and every test that uses the object runs the constructor first. Reversibility is trivial, since reverting the commit removes the fault. Blast radius is users and Change frequency is rarely, because fields change a few times a year. Test cost is trivial, but a constructor test finds no bug that the compiler and the other tests miss.
| When | Decision | Why |
|---|---|---|
| The constructor rejects bad input, such as an end date before the start date | Test: one unit test per validation rule that passes a bad value and expects the error | Detectability falls to eventually and Reversibility rises to with-effort: an invalid object is saved silently |
| Two parameters share a type and a swap moves money, as in a transfer between accounts | Test mandatory: run the transfer and check which account is debited | Blast radius rises to money, Detectability falls to eventually and Reversibility rises to costly: a reversed transfer ends in refunds |
| Several parameters share a type and the constructor is written by hand | Test: one test that passes a distinct value for each parameter and reads each field back | Detectability falls to eventually: a swapped assignment stores a plausible value of the right type |
| Callers of a published library rely on the constructor's defaults | Test the defaults: one assertion per documented default | Detectability falls to eventually and Reversibility rises to costly: callers see a changed default only after they upgrade |
| The constructor rejects a user under the legal minimum age | Test mandatory: the exact minimum age, one day under it, and a missing birth date | Blast radius rises to safety-or-legal and Detectability falls to never: an accepted minor looks like any other account |
| The class is Python with no type checker, and no other test creates the object | Test minimally: one test that builds the object and calls its main method | Detectability falls to same-day: a misspelled attribute fails only when a request reaches that code |
| The constructor opens a file, a socket, or a database connection | Do not test the constructor; move the connection into a factory method | Test cost rises to heavy and Detectability falls only to same-day, because a failed connection shows at startup |
What breaks if you don't test
A constructor that assigns fields fails when a field stays empty or gets the wrong argument. An empty field throws a NullPointerException or an AttributeError in the next test that touches the object, before the merge. A wrong argument of another type does not compile. A wrong argument of the same type does: this.start = end stores a plausible date, and a report on the range comes back empty days later.
What you lose if you over-test
When the argument types differ, a test that asserts each field equals its argument copies the constructor line by line and breaks on every added, renamed, or removed field. In a codebase with 200 model classes, a rule to test every constructor adds 200 tests that fail only on intended changes and inflate coverage without checking how any object uses its fields.
What to do instead
Test the constructor through unit tests of the methods that use the object. Let the compiler check assignments: turn on strictPropertyInitialization in TypeScript, and mark fields final in Java. In Python, a dataclass generates the constructor, so no hand-written line can swap two fields. For validation inside a constructor, write one test per rule with pytest.raises or your framework's equivalent.
When the answer changes
- The constructor contains an
if, a calculation, or a parse, so it can be wrong without a crash. - You wrote the constructor by hand and two parameters share a type, such as
startandend. - No other test creates the object, and no compiler checks for unassigned fields.
Code example
Two tests for one constructor
Both tests pass, and I delete the first in code review.
from dataclasses import dataclass
from datetime import date
import pytest
@dataclass(frozen=True)
class DateRange:
start: date
end: date
def __post_init__(self):
if self.end < self.start:
raise ValueError("end is before start")
# Checks code that the dataclass generates, not code I wrote.
def test_stores_fields():
r = DateRange(date(2026, 1, 1), date(2026, 1, 31))
assert r.start == date(2026, 1, 1)
assert r.end == date(2026, 1, 31)
# Checks a rule. Fails when someone removes the guard.
def test_rejects_end_before_start():
with pytest.raises(ValueError):
DateRange(date(2026, 1, 31), date(2026, 1, 1))
The dataclass writes the assignments, so no line of mine can swap the two dates; with a hand-written __init__, I would keep the first test too. The __post_init__ guard turns a swapped call like DateRange(end, start) into an error instead of an empty report.
Related questions
FAQ
- Should you unit test constructors?
Unit test a constructor when it holds logic, such as validation or a default value, or when you wrote it by hand and two parameters share a type. Tests of the methods that read an object's fields already cover a constructor that assigns arguments of different types.
- How do I test a constructor that throws an exception?
Call the constructor with an invalid argument inside your framework's exception assertion, such as
pytest.raisesin Python orassertThrowsin JUnit. Write one test per validation rule and assert on the exception type, so a reworded message breaks nothing.- Should I test that a constructor sets default values?
Test a constructor default when callers outside your codebase rely on it, such as the default page size of a public API. In a published library, write one assertion per documented default.
- Should I test constructors of data classes and records?
Do not test constructors that the language generates, such as Kotlin data classes, Java records, and Python dataclasses, because nobody wrote their assignments by hand. Test only the validation you add, such as a Java record's compact constructor or a dataclass's
__post_init__method.