Constructor Initialization.
A test class initializes its fixture fields in a constructor instead of the framework's dedicated setup hook (setUp / @BeforeEach / TestInitialize), bypassing the test lifecycle.
##Signs and Symptoms
A test class has an explicit constructor that builds the system under test, mocks, or shared fields — work that belongs in the framework's setup hook (setUp() / @Before / @BeforeEach in JUnit, [TestInitialize] in MSTest, beforeEach in Jest/Vitest).
Tell-tale signs:
- The test class declares a constructor that assigns to
final/instance fields. - Fixture setup lives outside the lifecycle the framework actually manages — so
@BeforeEach/@AfterEach, base-class setup, and per-test re-initialization are silently bypassed or run in a confusing order relative to the constructor. - Asynchronous setup is impossible: you cannot
awaitinside a constructor, so async fixture work gets forced into the wrong place or duplicated.
Canonical JUnit form (from the test-smells catalog):
public class TagEncodingTest extends BrambleTestCase {
private final CryptoComponent crypto;
private final SecretKey tagKey;
public TagEncodingTest() { // smell: init in constructor
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
tagKey = TestUtils.getSecretKey();
}
}
JS/TS analog — fixture built once at describe/module scope (the constructor-equivalent) instead of per test:
describe('Cart', () => {
const cart = new Cart(); // built once, leaks state across tests
it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
it('starts empty', () => { expect(cart.size).toBe(0); }); // fails: sees apple
});
##Reasons for the Problem
Why it happens
- Developers unfamiliar with the purpose of the setup hook reach for the language feature they already know — the constructor — to initialize fields.
- It looks cleaner:
finalfields plus a constructor feel idiomatic, and an IDE may even generate the constructor for you. - Cargo-culting from production classes, where constructor initialization is the right pattern.
Why it hurts
- Bypasses the test lifecycle. Frameworks promise fresh state per test via the setup hook and an
@BeforeEach/@AfterEach(orbeforeEach/afterEach) pair. Logic hidden in a constructor sits outside that contract, so cleanup, base-class setup, and ordering guarantees may not apply as expected. - State leakage and flakiness. In frameworks that reuse one instance (or when you stash the fixture in a shared/
describe-scoped variable), mutations from one test bleed into the next. Tests pass or fail depending on execution order — false confidence and intermittent failures. - No async setup. A constructor can't
await. Any setup that needs I/O, a DB connection, or a started server can't live there cleanly; people work around it with blocking calls or duplicated per-test code. - Readability/consistency. Reviewers and tooling expect fixtures in the conventional hook. Splitting initialization between a constructor and a setup method makes the real fixture hard to find and easy to get wrong.
Caveat — it's framework-dependent. This is not a universal rule. In xUnit.net the constructor is the idiomatic per-test setup (paired with IDisposable for teardown), and MSTest even ships an opposite, opt-in rule (MSTEST0020) preferring constructors over [TestInitialize]. Treat "Constructor Initialization" as a smell specifically where your framework provides a dedicated setup hook (JUnit, classic MSTest, NUnit) and you're sidestepping it.
##Treatment
Move field initialization out of the constructor and into the framework's setup hook, and prefer fresh per-test state over shared instances.
- Delete the test-class constructor; declare the fields and assign them in the setup hook (
@BeforeEach/setUp()for JUnit,[TestInitialize]for MSTest,beforeEachfor Jest/Vitest). - If setup is asynchronous, this is mandatory — the hook can
await; a constructor cannot. - In JS/TS, build the fixture inside
beforeEach(re-assigning alet) rather than once atdescribe/module scope, so each test gets a clean object. - Keep teardown symmetric: pair
@BeforeEachwith@AfterEach(or, in xUnit.net's constructor style, implementIDisposable).
Before → after (JUnit):
// before
public class TagEncodingTest {
private final CryptoComponent crypto = new CryptoComponentImpl(...);
}
// after
public class TagEncodingTest {
private CryptoComponent crypto;
@BeforeEach
void setUp() {
crypto = new CryptoComponentImpl(...);
}
}
Before → after (Jest/Vitest):
describe('Cart', () => {
let cart: Cart;
beforeEach(() => { cart = new Cart(); }); // fresh per test
it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
it('starts empty', () => { expect(cart.size).toBe(0); });
});
If the fixture is genuinely immutable and expensive (and your framework supports it), a one-time hook such as @BeforeAll/beforeAll is acceptable — but only for truly read-only shared state, never for objects tests mutate.
##Detected by
- tsDetect (Test Smell Detector) Constructor Initialization
- MSTest Roslyn analyzers MSTEST0019