Testing Economics

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.

Unit JUnit 5 + pure in-RAM mocks · milliseconds · 75% Integration Spring context + in-memory DB · seconds · 20% E2E Real browser and network · minutes · 5%
Antipattern: Inverted Ice Cream Cone

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.

Calculator: The Cost of Feedback Pick a 500-test suite and see how long it takes to know if something broke
500 × 5 ms ✓ 2.5 seconds — near-instant feedback
The AAA Pattern

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.

1. Arrange

Instantiate the class under test and prepare its arguments.

Calculadora calc = new Calculadora();
int a = 5;
int b = 10;
2. Act

A single call: the exact action the test verifies.

int resultado = calc.sumar(a, b);
3. Assert

The expected value is compared against the actual value.

assertEquals(15, resultado);
expected: 15 actual: 15 ✓ pass
@BeforeEach: A Clean Instance per Test Run the suite and see how every test starts with no shared state
testSumar() @BeforeEach → new Calculadora() —
testRestar() @BeforeEach → new Calculadora() —
testDividirPorCero() @BeforeEach → new Calculadora() —

Every instance is fresh: one test cannot leak state into the next.

Isolation with Test Doubles

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.

Mock PasarelaPago
when(pasarela.cobrar(pedido))
  .thenReturn(true);
Real Class Under Test PedidoService

Its business logic runs as-is; only its external collaborators are doubled.

verify(pasarela, times(1)).cobrar(pedido);
Mock InventarioRepository
when(inventario.hayStock(sku))
  .thenReturn(true);
Scenario Simulator Change the mock behavior and see how the service reacts
when(pasarela.cobrar(pedido)).thenReturn(true); ✓ Order CONFIRMED — verify(pasarela, times(1)).cobrar(...) passes
Layered Architecture

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.

@RestController ProductoController

Receives @RequestBody ProductoDTO, validates input, and returns a ResponseEntity.

@Service ProductoService

Applies pricing rules, discounts, and pure domain validations.

@Repository ProductoRepository

Talks to the database via SQL / Hibernate / JPA.

Without Dependency Injection Coupled to the real Service: impossible to inject a mock
public class ProductoController {
    ProductoService service =
        new ProductoServiceImpl();
}
With Constructor Injection The test passes a mock, no need to boot the Spring container
public class ProductoController {
    private final ProductoService service;

    ProductoController(ProductoService service) {
        this.service = service;
    }
}
End to End

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. 1
    Incoming HTTP request

    The client sends the JSON in the POST request body.

  2. 2
    Jackson → ProductoDTO

    Jackson deserializes the JSON into a Java ProductoDTO object.

  3. 3
    Controller → Service

    productoService.crear(dto) : the Controller delegates to the Service.

  4. 4
    Business rules

    The Service computes taxes and maps the DTO into the Producto domain entity.

  5. 5
    Persistence

    INSERT INTO productos ... : the Repository saves the entity to the relational database.

  6. 6
    HTTP 201 Created

    The Controller packages the response with the Location header of the new resource.