Avoid Flaky Tests Java | Fresher Interview
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
A flaky test changes result across equivalent runs. I first preserve evidence such as the random seed, test order, timing, logs, and environment, because rerunning until green loses the clue. Then I look for uncontrolled time, randomness, shared data, fixed sleeps, race conditions, resource pressure, and network or process boundaries. For time-based rules I inject a Clock. For asynchronous work I prefer a completion signal or a bounded wait on an observable condition; Awaitility is one helper, not a guarantee. Unit tests can replace an external boundary, while integration tests should use a controlled real dependency when that interaction is the subject. The fix may belong in production synchronization or cleanup rather than in the test itself.
Avoid Flaky Tests Java | Fresher Interview
Direct Answer
Flaky tests can pass or fail with the same code because an input is uncontrolled: time, scheduling, shared state, randomness, ports, files, databases, or remote services. Control the relevant boundary: inject a time/random source, isolate and reset state, wait on an observable condition for asynchronous work, and use a stable fake or controlled integration environment. Awaitility is one optional third-party waiting library, not part of JUnit.
Code Example
import java.time.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
// FIXED: inject Clock instead of calling now() directly
public class PromotionService {
private final Clock clock;
public PromotionService(Clock clock) { this.clock = clock; }
public boolean isPromotionActive(LocalDateTime start, LocalDateTime end) {
var now = LocalDateTime.now(clock); // controllable in tests
return now.isAfter(start) && now.isBefore(end);
}
}
class PromotionServiceTest {
@Test
void isPromotionActive_whenWithinWindow_thenReturnsTrue() {
var promoTime = LocalDateTime.of(2024, 12, 25, 12, 0);
var clock = Clock.fixed(promoTime.toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
var service = new PromotionService(clock);
boolean result = service.isPromotionActive(
promoTime.minusHours(1), promoTime.plusHours(1));
assertTrue(result); // always deterministic
}
}
Common Mistakes & Pitfalls
- Fixed sleep for async work — a longer sleep is still a timing guess. Prefer a completion signal or a bounded wait on an observable condition.
- Uncontrolled wall-clock time — inject
Clockwhen current time is an input, and make the relevant zone explicit. - Shared mutable state — isolate data per test or reset it safely;
@BeforeEachalone does not protect a static from parallel tests. - Assuming the test alone is wrong — investigate whether the failure reveals a real race, timeout, cleanup, or dependency problem.
- Rerunning without evidence — preserve logs, random seeds, order, and environment so the cause can be reproduced.
Check your understanding
Answer these without looking back at the article.
- What is Awaitility and how do you use it?
- How do you inject Clock for testable time-dependent code?
- What is WireMock?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.