Unit tests verify the smallest testable parts of an application in isolation. High-quality unit tests act as executable documentation and provide a safety net for refactoring.
Use the Arrange-Act-Assert (AAA) pattern to keep tests readable.
@Test
void shouldCalculateDiscountForPremiumUser() {
// Arrange
User user = new User("Alice", UserTier.PREMIUM);
PricingEngine engine = new PricingEngine();
// Act
double price = engine.calculatePrice(100.0, user);
// Assert
assertEquals(80.0, price, "Premium users should get a 20% discount");
}
Instead of writing five tests for five different inputs, use parameterized tests to map inputs to expected outputs. This is the standard for complex logic.
@ParameterizedTest
@CsvSource({
"10, 2.0",
"50, 10.0",
"100, 20.0",
"0, 0.0"
})
void shouldCalculateCorrectTax(double amount, double expectedTax) {
TaxCalculator calc = new TaxCalculator(0.20);
assertEquals(expectedTax, calc.calculate(amount));
}
Use mocks (e.g., Mockito) only for external boundaries or complex dependencies you don't control. Do not mock internal logic or data objects (POJOs/Records).
PaymentGateway that hits a 3rd party API.List or a simple User object.when(...).thenReturn(...) for a 5-line method, your class probably has too many responsibilities (SRP violation).