---
title: "Constructor Initialization"
type: "test-smell"
slug: "constructor-initialization"
url: "http://localhost:3000/en/test-smells/constructor-initialization.md"
category: "Fixture Smells"
description: "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."
---
# 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 `await` inside a constructor, so async fixture work gets forced into the wrong place or duplicated.

Canonical JUnit form (from the test-smells catalog):

```java
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:

```ts
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: `final` fields 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` (or `beforeEach`/`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.

1. Delete the test-class constructor; declare the fields and assign them in the setup hook (`@BeforeEach`/`setUp()` for JUnit, `[TestInitialize]` for MSTest, `beforeEach` for Jest/Vitest).
2. If setup is asynchronous, this is mandatory — the hook can `await`; a constructor cannot.
3. In JS/TS, build the fixture _inside_ `beforeEach` (re-assigning a `let`) rather than once at `describe`/module scope, so each test gets a clean object.
4. Keep teardown symmetric: pair `@BeforeEach` with `@AfterEach` (or, in xUnit.net's constructor style, implement `IDisposable`).

Before → after (JUnit):

```java
// 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):

```ts
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` (https://testsmells.org/pages/testsmells.html)
- **MSTest Roslyn analyzers** `MSTEST0019` (https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0019)
