The Testing Pyramid: Fast, Reliable, Cheap
A bug caught during development costs minutes to fix. The same bug in production costs reputation and thousands of dollars. The pyramid defines the ideal test proportion to catch errors as early as possible.
Too many brittle E2E tests that take hours to run, and integration suites so heavy nobody runs them locally. Feedback arrives late and the team stops trusting the tests.
Arrange, Act, Assert: The Anatomy of a Test
Every test answers a single question and follows 3 phases: prepare the scenario, run the exact action under test, and verify the outcome.
Instantiate the class under test and prepare its arguments.
Calculadora calc = new Calculadora();
int a = 5;
int b = 10; A single call: the exact action the test verifies.
int resultado = calc.sumar(a, b); The expected value is compared against the actual value.
assertEquals(15, resultado); Every instance is fresh: one test cannot leak state into the next.
Mocks with Mockito: Isolate Without Touching the Real World
Testing PedidoService should never connect to the real payment gateway or charge real money. A mock is a controlled double that answers exactly as instructed.
when(pasarela.cobrar(pedido))
.thenReturn(true); Its business logic runs as-is; only its external collaborators are doubled.
verify(pasarela, times(1)).cobrar(pedido); when(inventario.hayStock(sku))
.thenReturn(true); Controller, Service, Repository: Separating Responsibilities
A REST microservice strictly separates HTTP/JSON translation, business rules, and persistence. Constructor-based dependency injection is what makes each layer testable in isolation.
Receives @RequestBody ProductoDTO, validates input, and returns a ResponseEntity.
Applies pricing rules, discounts, and pure domain validations.
Talks to the database via SQL / Hibernate / JPA.
public class ProductoController {
ProductoService service =
new ProductoServiceImpl();
} public class ProductoController {
private final ProductoService service;
ProductoController(ProductoService service) {
this.service = service;
}
} The Lifecycle of a REST Request
A full trace of a POST /productos with a JSON payload, from the client to disk and back.
POST /productos { "nombre": "Café", "precio": 3.5 } - 1 Incoming HTTP request
The client sends the JSON in the POST request body.
- 2 Jackson → ProductoDTO
Jackson deserializes the JSON into a Java ProductoDTO object.
- 3 Controller → Service
productoService.crear(dto): the Controller delegates to the Service. - 4 Business rules
The Service computes taxes and maps the DTO into the Producto domain entity.
- 5 Persistence
INSERT INTO productos ...: the Repository saves the entity to the relational database. - 6 HTTP 201 Created
The Controller packages the response with the Location header of the new resource.