A class you are testing creates a collaborator with `new` inside one of its methods, so there is no seam to inject a test double. How does Mockito's `mockConstruction(Class)` API let you take control of that instance, and what exactly does it intercept?
answer
- intercepts `new`, constructor body skipped
- MockedConstruction is AutoCloseable
- thread-local, scope-bounded
- needs inline mock maker (default in Mockito 5)
- constructed() = ordered list of mocks
basics
~20 sMockito.mockConstruction(Foo.class) opens a scope in which every new Foo(...) on the current thread returns a Mockito mock instead of a real object, and the real constructor body never runs. Closing the scope restores normal construction.
solid answer
~40 s`Mockito.mockConstruction(Foo.class)` returns a `MockedConstruction<Foo>` resource. While it is open, any `new Foo(...)` executed on the current thread is intercepted: the real constructor body is skipped and the expression evaluates to a Mockito mock whose methods return default answers (null, 0, false, empty collections). It is used with try-with-resources, because the interception is registered thread-locally and must be unregistered. ```java try (MockedConstruction<Foo> mocked = Mockito.mockConstruction(Foo.class)) { service.doWork(); // internally does new Foo(...) verify(mocked.constructed().get(0)).send("payload"); } ``` It needs the inline mock maker — default since Mockito 5, opt-in via the `mockito-inline` artifact on 3.4–4.x. It affects only constructions inside the scope on that thread; instances built earlier or on another thread are untouched. Treat it as a fallback for code you cannot refactor to constructor injection.
code
java · 16 linesclass ReportService {
void send(String body) {
new MailClient("smtp://host").deliver(body);
}
}
@Test
void deliversBody() {
try (MockedConstruction<MailClient> mocked =
Mockito.mockConstruction(MailClient.class)) {
new ReportService().send("hello");
assertEquals(1, mocked.constructed().size());
verify(mocked.constructed().get(0)).deliver("hello");
}
}go deeper
Know that it exists and what it does in one sentence: inside the scope, new Foo(...) yields a mock. Be able to write the try-with-resources block.
Explain that the constructor body is skipped, that the scope is thread-local and must be closed, and that the mock starts unstubbed.
Diagnose the classic failures — empty constructed() because of threading or scope ordering, NPEs from unstubbed defaults, leaked scopes poisoning later tests — and say when a refactor beats interception.
Frame it as a testability smell detector: it buys coverage on code you do not own, but a codebase that needs it routinely has a dependency-inversion problem worth paying down.
## Why the API exists A test double only helps if the code under test can be given one. When a class writes `new HttpClient(url)` inside a method body, the dependency is hard-coded into the bytecode: there is no setter, no constructor parameter, no factory to override. Historically the options were to refactor the class or to reach for a bytecode-rewriting tool. Since Mockito 3.4.0, Mockito itself can intercept construction. ## What the call does `Mockito.mockConstruction(Foo.class)` installs, for the current thread, an interceptor on every constructor of `Foo`. From that moment until the scope closes: - `new Foo(anything)` allocates the object but does **not** execute the constructor body — no field initialisation, no side effects, no exceptions from validation logic in the constructor. - The reference handed back to the caller is a Mockito mock, so every method returns the default answer for its return type and records invocations. - Each construction appends the resulting mock to an ordered list you can read with `constructed()`. The returned `MockedConstruction<Foo>` implements `AutoCloseable`. `close()` removes the interceptor, after which `new Foo(...)` behaves normally again. ## Scope rules that bite people **Thread-local.** The registration lives in a thread-local, so construction on a worker thread, an executor, or an `@Async` call is not intercepted, and the mock list stays empty. **Scope-bounded.** Anything constructed before the scope opened is a real object and stays real. Singletons, Spring beans built at context startup, and objects cached in static fields are typical misses. **Must be closed.** Because registration is thread-local and Mockito refuses a second overlapping registration for the same type on the same thread, a leaked scope poisons every later test that runs on that thread. try-with-resources (or a strict open/close pair in setup/teardown) is the only safe shape. **Subclasses and interfaces.** You mock a concrete class; constructing a subclass is a different constructor and is not intercepted by mocking the parent type. ## What it does not do It does not stub anything by itself. Out of the box the constructed mock returns nulls, which frequently turns an untestable `new` into a `NullPointerException` two lines later. Stubbing is done with the two-argument overload that takes a `MockInitializer`, or by pulling the instance out of `constructed()` and stubbing it before the code path that uses it — which is only possible if construction and use are separated in time. It also does not help with static factory calls (`Foo.create()` returns an object without a `new` in your code path being interceptable at the call site), and it does not intercept construction inside JDK classes that the instrumentation deliberately leaves alone. ## Requirements Constructor interception needs the inline mock maker, which instruments loaded classes through a Java agent instead of generating subclasses. From Mockito 5 that is the default mock maker; on 3.4–4.x you add the `mockito-inline` artifact or set the `mock-maker-inline` resource. Without it the call fails at runtime. ## When to use it Good cases: legacy code you cannot change yet, third-party classes that construct expensive collaborators internally, a `new` on a hot path that opens sockets or spawns threads during a unit test. Bad case: your own code, where introducing a constructor parameter or a small factory interface is a five-minute refactor and leaves the test readable without bytecode magic.
- Does the real constructor body run when construction is intercepted?No. The object is allocated but the constructor body is skipped entirely, so field initialisers, validation, and side effects such as opening a connection never execute. That is usually the point — it is what makes an expensive or failing constructor harmless in a unit test — but it also means any state the constructor would set up simply is not there.
- Why does the interception stop working when the code under test constructs the object on a worker thread?Registration is stored in a thread-local, so only constructions executed on the thread that opened the scope are intercepted. A construction inside an executor task, a `CompletableFuture` stage, or an `@Async` method runs on another thread, gets a real object, and leaves `constructed()` empty. Either drive the code synchronously in the test or use a same-thread executor.
It is like swapping the factory floor for a prop shop: while the swap is in force, anyone who orders a new widget gets a hollow stage prop instead, and nobody on the production line notices the difference.
saying these in an interview costs you the question
- Claiming the constructor body still runs and only the methods are stubbed.
- Forgetting try-with-resources, then blaming an unrelated later test for the failure.
- Expecting objects created before the scope opened, or on another thread, to be mocks.
- Assuming the constructed mock returns sensible values — it returns nulls and zeros unless stubbed.
- Believing it works with the default subclass mock maker on older Mockito without `mockito-inline`.