Unit Testing
TL;DR
- Unit Testing focuses on testing the smallest testable part of an application (a single method or a single class) in complete isolation.
- Fast execution and 100% isolation are the core requirements.
- You must “mock” or “stub” external dependencies (databases, APIs, other classes) to achieve true isolation.
Concept
If you test a UserService that saves a user to a MySQL database, and the test fails, why did it fail? Is the logic in UserService broken? Did the database crash? Did the network go down? You don’t know.
That is not a Unit Test; that is an Integration Test.
A true Unit Test removes all external variables. You test the UserService by itself. When the service tries to talk to the database, you intercept it and provide a fake “Mock” database that instantly returns a hardcoded response.
Because Unit Tests never touch the network, the disk, or a real database, you can run 10,000 of them in 2 seconds. They form the massive base of the “Testing Pyramid”.
Examples
// THE CLASS WE ARE TESTING
public class OrderService {
private final PaymentProcessor paymentProcessor; // An external dependency
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
public boolean checkout(Order order) {
if (order.getTotal() <= 0) return false;
// We don't want to actually charge credit cards during tests!
return paymentProcessor.charge(order.getTotal());
}
}
// THE UNIT TEST
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class OrderServiceTest {
@Test
void checkout_ReturnsTrue_WhenPaymentSucceeds() {
// 1. ARRANGE
Order order = new Order(100.0);
// Create a fake processor (A Stub) that ALWAYS returns true
PaymentProcessor fakeProcessor = amount -> true;
OrderService service = new OrderService(fakeProcessor);
// 2. ACT
boolean result = service.checkout(order);
// 3. ASSERT
assertTrue(result);
}
}
Interview Questions
Q: What is the “Arrange-Act-Assert” (AAA) pattern?
A: It is the standard way to structure a unit test:
- Arrange: Set up the test data, configure mocks, and instantiate the class under test.
- Act: Call the exact method you are trying to test.
- Assert: Verify that the output (or the state changes) matches what you expected.
Q: What is Code Coverage, and is 100% coverage a good goal?
A: Code Coverage (e.g., JaCoCo) measures the percentage of your source code lines that were executed during your Unit Tests.
While high coverage (70-80%) is excellent for catching regressions, aiming for 100% coverage is an anti-pattern. It forces developers to write meaningless tests for simple getters/setters just to satisfy a metric, wasting time and making the test suite brittle to refactoring. You should test logic, not syntax.