ReviseAlgo Logo

Testing in Java

Testing Best Practices

Write clean, maintainable, fast, and deterministic tests following the F.I.R.S.T principles.

Interview: Frequently asked to rate testing maturity. Topics include test naming, assertion patterns, mocking boundaries, and handling concurrency.

Last Updated: June 13, 2026 8 min read

Writing tests is only half the battle; maintaining them is often harder. Good tests follow the F.I.R.S.T. principles (Fast, Independent, Repeatable, Self-Validating, Timely) and avoid common anti-patterns like flakiness.

Fast

Tests must run fast so developers execute them on every code edit, preventing regression accumulations.

Independent

Tests must not depend on the execution order or shared state of other tests.

Repeatable

A test must yield identical results in any environment (local machine, CI/CD pipeline, staging).

Structuring Tests: AAA Pattern

Tests should be structured using the Arrange, Act, Assert (AAA) pattern (or Given-When-Then in BDD):

  • Arrange (Given): Initialize target objects and configure all dependencies.
  • Act (When): Invoke the specific method or logic under test.
  • Assert (Then): Verify the output, state, or mock interactions against expected invariants.

Code Walkthrough

This class demonstrates a cleanly structured unit test using the AAA pattern and clear naming.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class AccountTest {

@Test void deposit_ValidAmount_UpdatesBalanceCorrectly() { // Arrange (Given) Account account = new Account(100.00);

// Act (When) account.deposit(50.00);

// Assert (Then) assertEquals(150.00, account.getBalance(), "Deposit should increase balance"); } }

Interview-Relevant Information

Q: What are flaky tests and how do you prevent them?
Answer: A flaky test passes and fails randomly on the same codebase without changes. Common causes include time-dependent assertions (using Instant.now()), thread concurrency races, and hardcoded pauses (e.g. Thread.sleep()). Prevent flakiness by injecting virtual Clock instances or using polling libraries like Awaitility.

Q: Why is mocking everything an anti-pattern?
Answer: Mocking internal helper classes (e.g. mocking ArrayList or basic utils) is unnecessary overhead. Only mock boundary interfaces (gateways, remote APIs, storage classes). Mocking everything creates brittle tests that require constant updates during simple refactoring.

Quick Checklist

Are your test names descriptive? Do you separate your test setup from execution logic? If yes, you are writing high-quality tests.

Use Cases

Structuring code suites cleanly for CI/CD pipeline automation.

Writing regression safety nets during major logic refactoring.

Common Mistakes

Asserting multiple unconnected business rules in a single unit test method.

Relying on specific system locales or time zones, resulting in tests failing on remote build servers.